New Attributes Traits

Blog Post created by pmartin Employee on Nov 3, 2016

What are Traits

Traits are a new way of identifying attributes with AF 2016.  Traits can be attached to AF Attributes and are designed to generically identify common characteristics or behaviors. Specifically, a trait can be used to define and/or find related AF Attribute objects with well-known behaviors and relationships. Starting with the AF SDK 2016, users are able to create, edit, delete, view, and utilize their attribute traits

To get a better idea of what traits are, let’s take an example from an application that makes use of traits: PI Coresight.  Coresight can find limits automatically and trend them without the need to know the exact trait attribute name.  See the video here: OSIsoft: Configure Attribute Traits in AF (Asset Framework) to Set Limits [AF 2016].  In this video, attribute names were chosen to match the names of their associated traits. Please be aware that this is not necessary; traits are designed to be generic and independent from the attribute name.


Types of Traits

There are 3 types of traits in the AF 2016 release. Additional trait types such as longitude and latitude will be rolled out in later versions of AF Server.

Limit Attribute Traits

Identify expected range of process variables. Available Limit Attribute Traits are Minimum, LoLo, Lo, Target, Hi, HiHi, and Maximum.

Forecast Attribute Traits

Use predicted values to make comparisons to your current values.

Analysis Start-Trigger Attribute Traits

For Event Frame Templates, store the Analysis trigger name or expression that caused the Event Frame to start.


Why use them

Traits provide context for attributes. Attributes like minimum value, maximum value, or forecasted value are universal so it doesn’t matter whether you, as the developer, know the process or not.  Want to make a program that validates whether the values coming in to a system are normal? Easy! Just search for the minimum and maximum trait attributes and compare it to the current value of its parent attribute.



If you are planning on following along, a few things need to be configured before you run the code. I have attached an XML export of my Traits database that you will need to import. Please note that the code used in this project is hardcoded to look at the “Traits” database.  I suggest creating a new database with that name and importing the XML file there.  If you don’t want to create a new database, you will need to modify the line of code that has:

AFDatabase db = AF.Databases["Traits"];

so that it is looking at your database and not the “Traits” database.


Creating Traits

Let’s start creating some trait attributes.  First, we’ll find the template that was imported in the previous section.

AFElementTemplate temperatureTemplate = db.ElementTemplates["Temperature_Simple"];

This template has two attributes:  Humidity and Temperature.  These two attributes have values that are randomly generated.  See the attached source code file is you wish to change these values from their initial value.

In this next section of code, we find the Temperature attribute and check if the Hi Limit attribute trait has already been created.  If it hasn’t, we create it using the value of its Abbreviation property for its name.  We then repeat this process for the Lo attribute trait. In this implementation of the code, the Humidity attribute is left unused so that you can experiment with other limit traits if you so desire.


//If Hi Trait doesn't exist, create it
if(temperature.AttributeTemplates[AFAttributeTrait.LimitHi.Abbreviation] == null) {
       //create new attribute
       AFAttributeTemplate limitHi = temperature.AttributeTemplates.Add(AFAttributeTrait.LimitHi.Abbreviation); 
       //specify which trait this attribute is supposed to be
       limitHi.Trait = AFAttributeTrait.LimitHi;
       //set the value for the Hi Limit
       limitHi.SetValue(100, limitHi.DefaultUOM);

//If Lo Trait doesn't exist, create it
if (temperature.AttributeTemplates[AFAttributeTrait.LimitHi.Abbreviation] == null) {
       AFAttributeTemplate limitLo = temperature.AttributeTemplates.Add(AFAttributeTrait.LimitLo.Abbreviation);
    limitLo.Trait = AFAttributeTrait.LimitLo;
    limitLo.SetValue(0, limitLo.DefaultUOM);


Once this code has been run, our template is defined.  Next, we'll utilize a for loop to initialize 100 elements using this template with random values for Humidity and Temperature.


for (int i = 0; i < 100; i++) {
       AFElement e = db.Elements.Add("Location"+ i);
       e.Template = temperatureTemplate;
       e.Attributes["Humidity"].SetValue(new AFValue(r.Next(0,100)));
       e.Attributes["Temperature"].SetValue(new AFValue(r.Next(-20, 120)));


Using Traits

As I discussed earlier in this post, traits can be used to quickly find whether your values are out of range.  That’s exactly what we’re going to do in this section.  I’ve outlined two different approaches for doing the same task.  The first method searches by Attribute trait name. This approach can check attributes across multiple templates but will only work if your trait attributes are consistently named.  The second approach searches by parent attribute name.  This approach will only check the attributes with a particular name, but it will do so much faster than the first approach.  In addition, this approach allows us to grab attribute traits with a wide variety of names.


Search by Attribute Traits

First, we’ll find all the attributes corresponding to our Lo limit attribute.  We will utilize the FindElementAttributes method to find attributes that have the same name as the abbreviation for the Lo Limit.  If you recall from the previous section, we used the Trait abbreviations to name our Limit attributes.


AFAttributeList los = AFAttribute.FindElementAttributes(db, null, null, null, null, AFElementType.Any, AFAttributeTrait.LimitLo.Abbreviation, null, TypeCode.Double, true, AFSortField.Name, AFSortOrder.Descending, 101);


Next, we’ll preload the values for Lo so that the performance is better.  The style of preloading uses bulk calls.  See the New Features and Best Practices Webinar and the comparison between Serial vs Parallel vs Bulk calls KB article for more information.  The values for Lo will all be the same in this simple example but it’s a bad habit to assume that it will always be the case.  Limits can be configured on a per Element basis or can be configured to get the value from a PI Point, Table Lookup, as well as other sources.


AFValues lovals = los.GetValue();


Limits (and Forecasts) are children of the attribute they are to be compared to.  In this next step we will loop through every returned Lo attribute and compare it with the parent to determine whether the parent value is out of range.


for (int i = 0; i < los.Count; i++) {
       //if the lo value is greater than the parent (attribute) value, output to console
       if(lovals[i].ValueAsDouble() > los[i].Parent.GetValue().ValueAsDouble()) {
         Console.WriteLine(   "Value under Lo Limit for {0}  :  {1} < {2}", 


We then repeat the process for our Hi Limit to determine which values have a high temperature.


Our output looks something like this:

Value under Lo Limit for Location9  :  -8 < 0

Value under Lo Limit for Location0  :  -20 < 0

Value over Hi Limit for Location98   :  110 > 100

Value over Hi Limit for Location96   :  114 > 100


Search by Parent Attribute

First, we’ll have to find the list of parent attributes.  We will utilize the FindElementAttributes method to find attributes named Temperature that are under an element that uses the Temperature_Simple template.   We’ll preload load values from this attribute similar to how we did in the previous section.


//Get Temperature Template
AFElementTemplate temperatureTemplate = db.ElementTemplates["Temperature_Simple"];
//Find all attributes that belong to an element of this template
AFAttributeList al = AFAttribute.FindElementAttributes(db, null, null, null, temperatureTemplate, AFElementType.Any, "Temperature", null, TypeCode.Double, true, AFSortField.Name, AFSortOrder.Ascending, 200);
//load values
AFValues alval = al.GetValue();


Now that we have the values, we’ll loop through each associated attribute and get its traits by using the GetAttributeByTrait method.  The nice thing about this method is that even if we had created our attribute traits with the crazy name below, it would still find accurately find and identify the limit.


AFAttributeTemplate limitHi = temperature.AttributeTemplates.Add("Some crazy high limit name");


After verifying that we found an attribute trait by checking for null, we compare the two to see if the value is out of range.


foreach(AFValue v in alval) {
     //get the trait values
     AFAttribute lo_a = v.Attribute.GetAttributeByTrait(AFAttributeTrait.LimitLo);
     AFAttribute hi_a = v.Attribute.GetAttributeByTrait(AFAttributeTrait.LimitHi);

     //compare to trait values if the trait exists
     if(hi_a != null && v.ValueAsDouble() > hi_a.GetValue().ValueAsDouble()) {
          Console.WriteLine("Value over Hi Limit for {0}  :  {1} > {2}",
     else if(lo_a != null && v.ValueAsDouble() < lo_a.GetValue().ValueAsDouble()) {
              Console.WriteLine("Value under Lo Limit for {0}  :  {1} < {2}", 


Similar to the first method, the output results look like this:

Value over Hi Limit for Location98     : 110 > 100

Value under Lo Limit for Location85  :  -8 < 0

Value over Hi Limit for Location79     : 112 > 100

Value under Lo Limit for Location72  :  -10 < 0


The only difference is that Lo and Hi limits are interleaved.



I hope you can see the benefits of using traits even in this contrived example. Even without knowing how or where the temperature was measured, you could instantly tell whether the values you were seeing were expected. More trait types will be coming in the future so you should get on board now!

If you liked this post and want to learn more about traits, let us know.  In addition to some more tips/tricks, I have content on Forecasts and Analysis Start Condition traits that I would love to share.  If you want to learn more about them on your own, the examples of those are included in the source code in the “COMPLEX EXAMPLE” region.

I have attached my source code and an export of my database so that you experiment and modify as much as you’d like.  The code is also available on GitHub.