I am trying to use AFElementSearch in the AF SDK and I used the Search feature from the PI System Explore to create my query string, but I am getting a "Type required" error at runtime. Below are the details. Any help would be appreciated.

I am trying to use AFElementSearch in the AF SDK and I used the Search feature from the PI System Explore to create my query string, but I am getting a "Type required" error at runtime. Below are the details. Any help would be appreciated.

Here is my search from PI System Explorer

Here is the source code. The AFDB object has a valid AF database object defined. I have commented out some of the original code and hard coded the query string just for testing purposes.

  '*********************************************************************

    '* Function Name.: FindElementsByCategory

   '* Written By....: Bryan Sower

    '* Written On....: 01-Dec-2024

    '* Purpose.......: This will return a collection of AFElement objects

   '*                which have the defined category assigned. If the

   '*                objParentElement is defined it will limit the search

   '*                to only child elements.

   '*********************************************************************

    '* Variables.....: CategoryName    - Category to find objects for

   '*                objParentElement - AFElement object for parent element

   '*                CacheTimeout    - Timeout for cache

   '*                Return          - IEnumerable(Of AFElement)

   '*********************************************************************

    '* NOTES:

   '* https://pisquare.osisoft.com/s/Blog-Detail/a8r1I000000H72cQAC/whats-new-in-afsearch-2105-pi-server-2018-sp2

   ' https://docs.aveva.com/bundle/af-sdk/page/html/search-example.htm '** Use This One **

   '*********************************************************************

    '* MODIFICATIONS

    '*********************************************************************

    '* Rev. Date               Initials Description

    '* ---- ------------------- -------- ---------------------------------

    '* 0001

    '*********************************************************************

   Public Function FindElementsByCategory(ByVal CategoryName As String, Optional ByRef objParentElement As AFElement = Nothing) As IEnumerable(Of AFElement)

       Dim RtnName As String = "FindElementsByCategory"

       Dim QryStr As String = String.Format("Category:'{0}'", CategoryName)

       Dim ElmtQry As AFElementSearch = Nothing

       Dim SearchElmts As IEnumerable(Of AFElement) = Nothing

 

       'If (Not objParentElement Is Nothing) Then

       '   QryStr = String.Format("Parent:'{0}' AllDescendants:true Category:'{1}'", objParentElement.GetPath(), CategoryName)

       'End If

 

       'LogMsgInfo(3, String.Format("Searching for elements using query string of {0}", QryStr), RtnName)

 

       QryStr = "Category:'Edict:ARSrxr_PhaseTimes'"

 

       ElmtQry = New AFElementSearch(AFDB, RtnName, QryStr)

       'ElmtQry.CacheTimeout = TimeSpan.FromMinutes(10)

 

       SearchElmts = ElmtQry.FindObjects()

 

       LogMsgInfo(3, String.Format("Elements found = {0}", SearchElmts.Count), RtnName)

 

       Return (SearchElmts)

 

   End Function

Here is a screen shot of the error:

  • Hi Bryan,

     

    First, I am not an AVEVA employee. However, I am a former OSIsoft employee and author of the blog post you reference.

     

    What you think is a screen shot at the bottom of your post is actually a link to one of my old blog posts. So I can't see your error.

     

    What I can pass on is that for an AFElementSearch that your query string needs to explicitly specify either an Element Template OR an Element Name or Naming Pattern.

     

    Also, the variable SearchElmts is an IEnumerable(Of AFElement). You really want this to be a List(Of AFElement) so that you can actually materialize and persist the found elements. You may need to alter like this:

     

    SearchElmts = ElmtQry.FindObjects().ToList()

     

    If you have further questions, just ask.

    Rick Davin

     

  • Thank you for your response not sure why my screen shot images did not stay in the original post. I will try again in this response. Please see below. If they do not show up, please let me know.

    As for your comment regarding the search string needing to specify either an element template or a name or pattern, not sure why that is needed. I am looking for all elements with a specific category defined. I may have some scenarios where I am only interested in any descendants under a root element. I am attempting to use the element search under PI System Explorer to build up my search string. I will make the change as you suggest for the variable Search Elements

     


    PSE Search Screenshot.jpg
    
    VS AFElementSearch Error.jpg

  • Let's try something.

     

    Near the top, comment out this line:

    'Dim ElmtQry As AFElementSearch = Nothing

     

    Further in code, change to this. OBSERVE: I am not specifying a Type. Using the Debugger, inspect what Type is being dynamically assigned to ElmtQry.

    Dim ElmtQry = New AFElementSearch(AFDB, RtnName, QryStr)

     

    Other things you did not ask about:

    Much of the style looks like VBA, where you had to declare all variables at the top. VB.NET allows the variable to declared in a more localized scope when you are ready to use it.

     

    It may be a matter of personal preference, but I do find it more readable to use Interpolated Strings, especially when dealing with many parameters:

    Old: String.Format("Category:'{0}'", CategoryName)

    New: $"Category:'{CategoryName}'"

     

    While C# dominates the many examples, back when I was an OSIsoft employee, I wrote this helpful blog:

    csharp-cheat-sheet-for-vbnet-developers

     

     

  • Another thing, this Search Overview Link shows there are 2 filters Category and CategoryName. Two things to try:

     

    One, use CategoryName instead. Example:

    QryStr = "CategoryName:'Edict:ARSrxr_PhaseTimes'"

     

    Though this may still be a problem. What I am wondering here is if the colon needs to be escaped, as it is the only real oddity about your dilemma. AFSearch must parse your query string into tokens, then rebuilds its own internal query string that is passed up to the AFServer (SQL Server), and that query string may have colons in it.

     

    Two, find the actual Element Category object, and use its Guid as Category. Assuming the variables AFDB and CategoryName are properly set, try:

     

    ' Assumes AFDB is a connected AFDatabase object
    ' Assumes CategoryName is a non-null String.
    Dim elemCategory = AFDB.ElementCategories(CategoryName)
    Dim QryStr = $"Category:'{elemCategory.ID.ToString("B")}' "

     This has the advantage of getting around the colon in the name, as the AF SDK call on line 3 will not try any parsing. It takes the input name as-is. The last blank before the ending double quote on line 4 is intentional just for MY EMPHASIS. You can omit it if you wish.

     

    And this blog post includes links to other related blog posts.

     

     

  • Thank you for your assistance with this issue. I did make the change you suggested without setting the object type. From the debugger it returned the same error as it did before which is error BC30182: Type expected. "Category:""Edict:ARSrxr_PhaseTimes"" Root:""HSC\%DCC\0160""" .

     

    Regarding your comment style, I suppose I am a little old school, and I like to define and initialize my variables at the top of the routine where possible. I primarily program in C# as well, but this effort is related to work we are doing to move ACE equations to EDICT and trying to use AF instead of the MDB.

     

    I am still wondering why it is generating an error regarding the query string. I would think I should be able to use the same query string from PI System Explorer Search --> Element Search in this code without any errors.

  • OK, I was able to resolve this. Turns out the message returned on the AFElementSearch object of error BC30182: Type expected. "Category:""Edict:ARSrxr_PhaseTimes"" Root:""HSC\%DCC\0160""" appears to be a bit of a red herring. Once I changed Dim SearchElmts As IEnumerable(Of AFElement) to Dim SearchElmts As List(Of AFElement) and then used SearchElmts = ElmtQry.FindObjects().ToList() everything worked fine. Thank you for your help!

  • For future reference for others here is what I ended up with for my code

     

      Public Function FindElementsByCategory(ByVal CategoryName As String, Optional ByRef objParentElement As AFElement = Nothing) As List(Of AFElement)

        Dim RtnName As String = "FindElementsByCategory"

        Dim QryStr As String = String.Format("Category:'{0}'", CategoryName)

        Dim ElmtQry As AFElementSearch = Nothing

        Dim SearchElmts As List(Of AFElement) = Nothing

     

        If (Not objParentElement Is Nothing) Then

          QryStr = String.Format("Root:'{0}' AllDescendants:true Category:'{1}'", objParentElement.GetPath(), CategoryName)

        End If

     

        ElmtQry = New AFElementSearch(AFDB, RtnName, QryStr)

        ElmtQry.CacheTimeout = TimeSpan.FromMinutes(10)

     

        SearchElmts = ElmtQry.FindObjects().ToList()

     

        Return (SearchElmts)

     

      End Function

     

  • Hello ​ 

    Thank you for following up AND posting your working solution. This potentially could help others in the future.

    Only minor thing to add is that this is acceptable, recommended by Microsoft, and reads better to me personally:

    If (objParentElement IsNot Nothing) Then
        ' note that IsNot is one word

    If you perform a general browser search for Microsoft "Visual Basic Coding Conventions", you will see it on the section Use the IsNot Keyword

    Good luck,

    Rick Davin

  • Thanks, I will make the change. I believe both accomplish the same thing, but IsNot is a little cleaner.

  • You do have a working solution, but it bothers me that I decent explanation was not given for the original error. I believe I have one.

     

    Allow me to reshow the pertinent lines of your original post:

    Dim ElmtQry As AFElementSearch = Nothing
    Dim SearchElmts As IEnumerable(Of AFElement) = Nothing
    
    ' Next line configures the search but no search occurs
    ElmtQry = New AFElementSearch(AFDB, RtnName, QryStr)
    
    ' Despite the action verb of Find, this line does attempt to NOT find anything.
    ' It is a IEnumerable, which is deferred execution.
    SearchElmts = ElmtQry.FindObjects()
    
    ' Here you ask for a Count, which attempts to execute the search
    ' but VB runs into a Type conversion
    LogMsgInfo(3, String.Format("Elements found = {0}", SearchElmts.Count), RtnName)

    The things to understand is that AFSearch is usually a Forward-only process and that the IEnumerable used here deferrs any execution until you actually try to enumerate over the collection. Things that enumerate over a collection: a ForEach loop, a ToList() call, or other LINQ calls such as Count().

     

    Note the last one: Count(). It has parenthesis because it is a LINQ method, and not a List.Count property without parentheses. However, VB is usually lenient in syntax and will try to implicitly perform Type conversions for you. There are cases where you can omit parantheses from methods and treat the VB Function as a Property. One example is ToString(). In VB, you can simply call it with ToString. This only works when you do not have a Function and a Property using the same name, such as Count() and Count.

    So VB.NET does not flag a syntax error during compile time. It is only during run-time that it has the IEnumerable object and encounters the Count call, which it thinks is a property for a List (actually: IList) and since you have an IEnumerable, it throws the error. The confusing thing to me was that the error backed up to the assignment of New AFElementSearch. Again, that's because of the deferred execution. But the line of code that actually triggerred the error was with .Count.