Click or drag to resize
OSIsoft, LLC

Search Overview

This topic is an overview of the AFSearch based searches within the AF SDK.

This topic contains the following sections:

The AFSearch based searches within the AF SDK use a query string to define the objects that will be returned by the search. This provides a flexible way to define the criteria used for the search. The syntax used for this query string is described in the Search Query Syntax section below.

Each of the AFSearch search classes derive from the generic AFSearchT type. These search classes can either return AF SDK object headers, full AF SDK objects (by specifying the fullLoad parameter to the Find method), or just fields of the AF SDK objects (using one of the AFSearchFindObjectFields methods). The Object Field Search section describes returning object fields in more detail.

Description

To use a query based search, an instance of one of the AFSearch derived search classes must be created along with a query for the search criteria. The PISystemSupports(PISystemFeatures) method can be used with the PISystemFeaturesQuerySearch or PISystemFeaturesQuerySearchAnalysis feature flags to determine which search classes are supported by the server. If the PISystemFeaturesQuerySearch feature is supported, then the following search classes can be used:

If the PISystemFeaturesQuerySearchAnalysis feature is supported, then the following search classes can be used: If the PISystemFeaturesQuerySearchAttribute feature is supported, then the following search classes can be used: The search query can be specified as a string or a list of search tokens. A list of search tokens can be generated using the Parse method of the specific search class to parse a search query string. An AFSearchToken consists of a filter, an operator, a filter value, and a list of nested tokens for a nested query. The nested tokens are used to perform a sub-query within the main query.

Once an instance of a search class has been created, one of the Find methods of the search class can be used to search for objects from the AF Server. If you only need a list of unique identifiers for each returned object, then use the AFSearchFindObjectIds method. If only specific fields of an object are needed (e.g. when displaying information about an object on a web page), then use one of the AFSearchFindObjectFields methods. To return full objects, use the specific Find method for the search class (e.g. AFElementSearchFindElements). An overview example of using search can be found in the Search Example topic. Also, each search method will have specific examples showing how to use the search method.

Object Field Search

There are three overloads of the AFSearchFindObjectFields method that provide a light-weight search that returns fields of objects instead of full AF SDK objects. One of these methods should be used when only some of the object's fields are required. Even though it is not as optimized on servers before PI AF 2017 (2.9.0), these methods still perform better than returning the full object. The query criteria for the search class is used to select which objects are returned. Specifying the fields of the object to be returned is similar to specifying a SELECT in a SQL statement.

The first FindObjectFields overload accepts a string containing the names of the fields to be returned separated by spaces and returns a list of field values for each matching object. The order of the returned field values will match the order of the field names specified in the call. The following shows an example of how to use this method:

// Get the Database
PISystems myPISystems = new PISystems();
PISystem myPISystem = myPISystems.DefaultPISystem;
if (myPISystem == null)
    throw new InvalidOperationException("Default PISystem was not found.");
AFDatabase myDB = myPISystem.Databases[dbName];
if (myDB == null)
    throw new InvalidOperationException("Database was not found.");

// Create a search to find all the event frames created from the 'Event'
// template and its 'Level' attribute value is less than 90.
int count;
using (var search = new AFEventFrameSearch(myDB, "FindEventFields", @"Template:'Event' |Level:<90.0"))
{
    search.CacheTimeout = TimeSpan.FromMinutes(10);

    // Do the search
    // Return specified fields as object list.
    count = 0;
    Console.WriteLine("Find Object Fields:");
    foreach (var row in search.FindObjectFields("ID Name StartTime EndTime |Level"))
    {
        count++;
        foreach (var item in row)
        {
            Console.Write("{0}, ", item);
        }
        Console.WriteLine();
    }
    Console.WriteLine("Found {0} EventFrames.", count);
}

The second FindObjectFieldsTObject overload is similar to the first one but returns a user-defined object for each object that matches the search criteria using a factory method. The factory method will take the list of field values for each matching object and create the user-defined return object. The order of the field values used in the factory method will match the order of the field names specified in the call. The following shows an example of how to use this method to return an anonymous type and as a list of strings for each object's name:

// Get the Database
PISystems myPISystems = new PISystems();
PISystem myPISystem = myPISystems.DefaultPISystem;
if (myPISystem == null)
    throw new InvalidOperationException("Default PISystem was not found.");
AFDatabase myDB = myPISystem.Databases[dbName];
if (myDB == null)
    throw new InvalidOperationException("Database was not found.");

// Create a search to find all the event frames created from the 'Event'
// template and its 'Level' attribute value is less than 90.
int count;
using (var search = new AFEventFrameSearch(myDB, "FindEventFields", @"Template:'Event' |Level:<90.0"))
{
    search.CacheTimeout = TimeSpan.FromMinutes(10);

    // Do the search
    // Return specified fields as an anonymous type.
    count = 0;
    var foundItems2 = search.FindObjectFields("ID Name StartTime EndTime |Level",
        i => new { ID = i[0], Name = i[1], Start = i[2], End = i[3], Value = i[4] });
    Console.WriteLine("Find Object Fields using Anonymous Type:");
    foreach (var row in foundItems2)
    {
        count++;
        Console.WriteLine("ID={0}, N='{1}', ST={2}, ET={3}, V={4}",
            row.ID, row.Name, row.Start, row.End, row.Value);
    }
    Console.WriteLine("Found {0} EventFrames.", count);

    // Return event frame name as list of strings.
    count = 0;
    var foundItems3 = search.FindObjectFields("Name", i => i[0].ToString());
    Console.WriteLine("Find Object Names as list of strings:");
    foreach (var row in foundItems3)
    {
        count++;
        Console.WriteLine(row);
    }
    Console.WriteLine("Found {0} EventFrames.", count);
    // 
    // Return event frame security tokens as list and check security for current user.
    count = 0;
    var foundItems4 = search.FindObjectFields("SecurityToken", i => (AFSecurityRightsToken)i[0]);
    Console.WriteLine("Find Object SecurityTokens and Check Security:");
    foreach (var tokenList in foundItems4.ChunkedBy(500))
    {
        // Check Security using Windows Identity.
        var rights = AFSecurity.CheckSecurity(myPISystem, WindowsIdentity.GetCurrent(), tokenList);
        foreach (var rightsItem in rights)
        {
            Console.WriteLine($"  Security Rights for '{myPISystem.CurrentUserName}': {rightsItem.Key} = {rightsItem.Value}");
        }

        // Check Security using Identities.
        rights = AFSecurity.CheckSecurity(myPISystem, myPISystem.CurrentUserIdentities, tokenList, myPISystem.CurrentUserName);
        foreach (var rightsItem in rights)
        {
            Console.WriteLine($"  Security Rights for '{myPISystem.CurrentUserIdentityString}': {rightsItem.Key} = {rightsItem.Value}");
        }
        count += tokenList.Count;
    }
    Console.WriteLine("Found {0} EventFrames.", count);
}

The third FindObjectFieldsTObject overload uses dynamic type conversion to convert the field values for each matching object into the properties and fields of a user-defined type. The user-defined type will define which fields are returned for each of the objects. By default, the field and/or property names in the user-defined type must match the desired object field names to be returned. If they do not match, then they will be skipped. The AFSearchObjectFieldAttribute can be used to specify the object field name to be used for the field or property instead of relying on the default mapping. The following shows an example of how to use this method with the user-defined type EventFrameFields:

//*************************************************************************
/// <summary>
/// This class defines the object fields returned from the 
/// FindObjectFields method.
/// </summary>
public class EventFrameFields
{
    // Field mapped using default name.
    public Guid ID;
    // Property mapped using default name.
    public string Name { get; set; }
    // Field mapped using 'ObjectField' attribute.
    [AFSearch.ObjectField("Duration")]
    public AFTimeSpan EFDuration;
    // Property mapped using 'ObjectField' attribute.
    [AFSearch.ObjectField("StartTime")]
    public AFTime EFStart { get; set; }
    // Attribute value mapped to property using 'ObjectField' attribute.
    [AFSearch.ObjectField("|Level")]
    public AFValue Level { get; set; }
}

//*************************************************************************
// Get the Database
PISystems myPISystems = new PISystems();
PISystem myPISystem = myPISystems.DefaultPISystem;
if (myPISystem == null)
    throw new InvalidOperationException("Default PISystem was not found.");
AFDatabase myDB = myPISystem.Databases[dbName];
if (myDB == null)
    throw new InvalidOperationException("Database was not found.");

// Create a search to find all the event frames created from the 'Event'
// template and its 'Level' attribute value is less than 90.
int count;
using (var search = new AFEventFrameSearch(myDB, "FindEventFields", @"Template:'Event' |Level:<90.0"))
{
    search.CacheTimeout = TimeSpan.FromMinutes(10);

    // Do the search
    // Return specified fields using dynamic type conversion.
    count = 0;
    var foundDynItems = search.FindObjectFields<EventFrameFields>();
    Console.WriteLine("Find Object Fields using Dynamic Type Conversion:");
    foreach (var row in foundDynItems)
    {
        count++;
        Console.WriteLine("ID={0}, N='{1}', ST={2}, D={3}, V={4}",
            row.ID, row.Name, row.EFStart, row.EFDuration, row.Level);
    }
    Console.WriteLine("Found {0} EventFrames.", count);
}

The following chart shows which field names are supported by each search class. Most fields are returned as strings. A detailed description about the fields and their return type can be found in the documentation for the specific FindObjectFields method (e.g. StartTime is returned as an AFTime). Value fields are specified as a path to the attribute relative to the returned object (e.g. "|Level|LowAlarm").

Search Query Syntax

This is the definition of the syntax used when specifying the search criteria.

Search Query syntax described in Extended Backus-Naur Form (EBNF)
Query             = { OrQuery | ConfigFilter }

OrQuery           = AndQuery { "OR" AndQuery } ;

AndQuery          = ParenQuery { [ "AND" ] ParenQuery }

ParenQuery        = "(" OrQuery { OrQuery } ")" | QueryFilter ;

QueryFilter       = StringValue   (* Defaults to 'Name:=' ⁽¹⁾ *)
                  | "Analysis" EqualOperator StringValue
                  | "AnalysisName" EqualOperator StringValue
                  | "CanBeAcknowledged" EqualOperator BooleanValue
                  | "Category" EqualOperator StringValue
                  | "CategoryName" EqualOperator StringValue
                  | "Contact" EqualOperator StringValue
                  | "ContactName" EqualOperator StringValue
                  | "CreationDate" Operator TimeValue
                  | "Description" EqualOperator StringValue
                  | "Destination" EqualOperator StringValue
                  | "Duration" Operator TimeSpanValue
                  | "Element" OptionalNestedOrInOperator
                  | "ElementName" EqualOperator StringValue
                  | "ElementReferenceTemplate" EqualOperator StringValue
                  | "End" Operator TimeValue
                  | "EventFrame" NestedQuery
                  | "GroupID" EqualOperator IntegerValue
                  | "ID" EqualOrInOperator
                  | "InProgress" EqualOperator BooleanValue
                  | "IsAcknowledged" EqualOperator BooleanValue
                  | "IsAnnotated" EqualOperator BooleanValue
                  | "IsInternal" EqualOperator BooleanValue
                  | "ModifyDate" Operator TimeValue
                  | "Name" EqualOperator StringValue
                  | "OwnerName" EqualOperator StringValue
                  | "Parent" OptionalNestedQuery
                  | "PlugIn" EqualOperator StringValue
                  | "PlugInName" EqualOperator StringValue
                  | "ReferenceType" EqualOperator StringValue
                  | "Root" EqualOperator StringValue
                  | "Severity" Operator SeverityValue
                  | "Source" EqualOperator StringValue
                  | "Start" Operator TimeValue
                  | "Status" Operator StatusValue
                  | "Target" EqualOperator StringValue
                  | "TargetName" EqualOperator StringValue
                  | "Template" EqualOperator StringValue
                  | "TemplateName" EqualOperator StringValue
                  | "Type" EqualOperator StringValue
                  | "Value" EqualOperator AttrValueFilter
                  | AttrValueFilter
                  ;

ConfigFilter      = "AllDescendants" EqualOperator BooleanValue
                  | "Sandbox" EqualOperator BooleanValue
                  | "SortField" EqualOperator SortFieldValue
                  | "SortOrder" EqualOperator SortOrderValue
                  | "TimeContext" EqualOperator TimeValue
                  | "TimeContextEnd" EqualOperator TimeValue

NestedQuery       = EqualOperator "{" Query "}" ;

OptionalNestedQuery
                  = EqualOperator StringValue
                  | NestedQuery
                  ;

OptionalNestedOrInOperator
                  = OptionalNestedQuery
                  | InOperator
                  ;

AttrValueFilter   = AttrPath Operator StringValue
                  | AttrPath InOperator
                  | AttrPath Operator StringValue AttrValueType
                  ;

AttrPath          = "'|" { QuotedEscapedChar | "''" } "'"
                  | ""|" { QuotedEscapedChar | """" } """
                  | "|" { EscapedChar }
                  ;

AttrValueType     = As Numeric
                  | As String
                  | As Guid
                  | As AFEnumerationSetName
                  ;

Operator          = EqualOperator | ":<>" | ":<" | ":<=" | ":>" | ":>=" ;

EqualOperator     = ":" | ":=" ;

InOperator        = ":IN(" StringValues ")" ;

EqualOrInOperator = EqualOperator StringValue | InOperator ;

SeverityValue     = "None"
                  | "Information"
                  | "Warning"
                  | "Minor"
                  | "Major"
                  | "Critical"
                  ;

SortFieldValue    = "ID"
                  | "Name"
                  | "Type"
                  | "StartTime"
                  | "EndTime"
                  ;

SortOrderValue    = "Ascending"
                  | "Asc"
                  | "Descending"
                  | "Desc"
                  ;

StatusValue       = "None"
                  | "NotReady"
                  | "Disabled"
                  | "Enabled"
                  | "Error"
                  ;

TimeValue         = "'" AFTimeString "'"
                  | """ AFTimeString """
                  | AFTimeString
                  ;

TimeSpanValue     = "'" AFTimeSpanString "'"
                  | """ AFTimeSpanString """
                  | AFTimeSpanString
                  ;

BooleanValue      = "'" Boolean "'"
                  | """ Boolean """
                  | Boolean
                  ;

IntegerValue      = "'" Integer "'"
                  | """ Integer """
                  | Integer
                  ;

StringValues      = StringValue { ";" StringValue } ;

StringValue       = "'" { QuotedEscapedChar | "''" } "'"
                  | """ { QuotedEscapedChar | """" } """
                  | { EscapedChar }
                  ;

QuotedEscapedChar = Char
                  | "\""      (* Escaped " character *)
                  | "\'"      (* Escaped ' character *)
                  | "\*"      (* Escaped * character *)
                  | "\?"      (* Escaped ? character *)
                  ;

EscapedChar       = NoWhiteSpaceChar
                  | "\" Char  (* The character is escaped *)
                  ;

Boolean           = "True" | "False" | "1" | "0" ;

Integer           = Digit { Digit } ;

Digit             = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;

AFEnumSetName     = ? Any string that matches the name of an AFEnumerationSet ?

AFTimeString      = ? Any string that can be parsed by AFTime.Parse ?

AFTimeSpanString  = ? Any string that can be parsed by AFTimeSpan.Parse ?

NoWhiteSpaceChar  = ? Any printable character except whitespace characters ?

Char              = ? Any printable character ?



⁽¹⁾ If a specific filter name is not specified, then the filter will default to
     "Name" and the operator will be "=". When a filter name is specified, no whitespace
     is allowed between the filter name, the ":" separator, and the optional operator.
     If the operator is not specified, the default operator is "=".

String values for name filters can contain wildcard characters that are described in the Wildcard Characters section below. The Filters section below describes the filters that can be used in the query and the Operators table below describes the operators that can be used in the filter conditions. The Attribute Value Query section provides more information about searching by attribute values.

Time strings are evaluated in the client's local time unless qualified with time zone information. The time string will be parsed using the current culture. If parsing with the current culture fails, then it will attempt to resolve the time string using the invariant culture.

An empty string value should be used when searching for objects with a null value for the specified filter. For example, to find all objects without a template defined use Template:'' in the query.

Wildcard Characters

The string value of a filter can be enclosed in single quotes ('), double quotes ("), or without quotes. The filter string value can include regular characters and wildcard characters. Regular characters must match exactly the characters specified in the filter value. Wildcard characters can be matched with arbitrary fragments of the filter value. Wildcard characters can be escaped using the single backslash (\) character.

When the filter value is specified within either single or double quotes, the single backslash (\) character is treated as a literal character unless followed by a wildcard character, a single quote ('), or a double quote ("). When specified within quotes, two quote characters that match the starting quote character are treated as a single quote character (e.g. '' is treated as a one single quote character ' if the filter value starts with a single quote). When the filter value is specified without quotes, the backslash character is always used to escape the next character. Therefore you must use a double backslash (\\) to match a single backslash when not using quotes around the filter value.

The wildcard characters used in the string value of a filter have the following rules:

  • If an empty string is specified as part of a Name filter, then everything will be matched. Otherwise, an exact match on empty string or default value for the filter is performed if an empty string is specified.

  • If no wildcards, then an exact match on the filter string is performed.

  • Wildcard * can be placed anywhere in the filter string and matches zero or more characters.

  • Wildcard ? can be placed anywhere in the filter string and matches exactly one character.

  • One character in a set of characters are matched by placing them within [ ]. For example, a[bc] would match 'ab' or 'ac', but it would not match 'ad' or 'abd'.

  • One character in a set of characters are not matched by placing them within [! ]. For example, a[!bc] would match 'ad', but it would not match 'ab', 'ac', or 'abd'.

  • A character in a range of characters from first to last are matched using the following syntax: [first - last]. For example, a[a-c] would match 'aa', 'ab', or 'ac', but it would not match 'ad' or 'abc'.

Filters

Each filter condition is separated by whitespace and will be ANDed together to perform an AFSearch based search query that can be used to search for objects from the AF Server. For more information about the query filters, see the AFSearchFilter topic.

The following chart shows which filters are supported by each search class.

The Sandbox filter is not supported by the AFEventFrameSearch if specified with any of the following filters: Analysis, Element, ElementName, or ElementReferenceTemplate. For an AFElementSearch, the Sandbox filter is not supported if the AllDescendants filter is also enabled. The following table describes the dependencies between search filters.

Filter

Notes

Example

AllDescendants

If this filter is enabled and the Root filter is also specified, then the search will be slower because more than one level of the hierarchy must be checked. This filter is enabled by default, so it is recommended to disable this filter if the Root filter is also specified and you do not need to search the full hierarchy under the specified root object.

Root:Tank101 ReferenceType:TankReference

ReferenceType

Finds objects with the specified reference type between the Root object and the found object. If the Root filter is not specified, then finds objects with the specified reference type between the found object and any parent object including the AFDatabase.

Root:Tank101 ReferenceType:TankReference

Value

A value filter requires that a Template filter must first be specified that can be used to find the AFAttributeTemplate specified in the value filter.

Template:MyTemplate#1 |Attr#1|Child#2:<=50

Operators

Search operators specify how the filter value is to be compared with the object's value for the filter. For more information about the search operators, see the AFSearchOperator topic.

The following table lists the operators used in the filter condition.

Operator

Description

Example

=

The Equal operator.

Name:Tank* or Name:=Tank*

<>

The NotEqual operator.

"|Attr#1":<>50

<

The LessThan operator.

"|Attr#1":<50

<=

The LessThanOrEqual operator.

"|Attr#1":<=50

>

The GreaterThan> operator.

"|Attr#1":>50

>=

The GreaterThanOrEqual operator.

"|Attr#1":>=50

IN()

The In operator.

"|Attr#1":IN(50;100;150) or Element:IN('Meter'; 'Tank')

Attribute Value Query

An attribute value query requires that a Template filter must first be specified that can be used to find the AFAttributeTemplate specified in the value filter. When using attribute value queries, search performance can be improved by marking the AFAttributeTemplate as being indexed by setting the IsIndexed property. The Type property of the attribute template determines the type of value to be queried, therefore the type must be defined and not set to a type of object. Any AFAttributeTemplate with a DefaultUOM specified must be of type Single or Double. If a DefaultUOM is not specified in the template for one of these types, then the search will be performed using the specified attribute value as being in the canonical units-of-measure which could cause unexpected results to be returned. The following table shows which AFSearchOperator is supported for each type of value being queried:

Type

Equal

NotEqual

LessThan

LessThanOrEqual

GreaterThan

GreaterThanOrEqual

In

Boolean

Yes

Yes

No

No

No

No

Yes

UInt64

Yes

Yes

No

No

No

No

Yes

String

Yes

Yes

No

No

No

No

Yes

Guid

Yes

Yes

No

No

No

No

Yes

Single

Yes

Yes

Yes

Yes

Yes

Yes

No

Double

Yes

Yes

Yes

Yes

Yes

Yes

No

All Others

Yes

Yes

Yes

Yes

Yes

Yes

Yes

When the Type property of the AFAttributeTemplate is String, then the following restrictions are placed on the string length of the attribute value used in the query:

IsIndexed

Operator

Length Limit

True

Equal or NotEqual

40 characters

True

In

40 characters

False

Equal or NotEqual

No limit

False

In

4096 characters when using PI AF Server 2.6 or greater; otherwise 40 characters

Query Syntax Examples

Below are some example queries:

  • Tank*

  • name:Tank*

  • Name:=Tank*

  • Name:=Tank* Template:MyTemplate#1

  • Name:=Tank* (Template:MyTemplate#1 OR Template:MyTemplate#2)

  • Name:=Tank* Template:""

  • Name:=Tank* Start:<T-1w End:>=*

  • Name:="*Tank*" Start:>*-3Days InProgress:True

  • Template:="My Template#2" End:<* "|Attr#1|Child#3":>32 '|Attr#1|Child#3':<="100"

  • Template:="My Template#2" End:<* "|Attr#1":IN('New York'; Boston; Miami)

  • Parent:{ Template:"My Template#2" }

  • Root:"RootEventFrame" AllDescendants:True

  • ID:="0f8fad5b-d9cb-469f-a165-70867728950e"

  • ID:IN('0f8fad5b-d9cb-469f-a165-70867728950e'; '7c9e6679-7425-40de-944b-e07fc1f90ae7')

Below are some example queries for an AFAttributeSearch:

  • Element:{ Template:"My Template#2" } Name:'Attr#1'

  • EventFrame:{ Template:"My Template#2" } Name:'Attr#1'

See Also
Enabling Operational Intelligence