Table of Contents

How to Work with Annotations and Extended Properties

Annotations are informational-only metadata with no impact on model behavior. They are useful for automation and scripting. Extended properties are intended for client tool extensions that require specific support. For example, field parameters in Power BI depend on extended properties, which is why this feature is Power BI-only.

Quick reference

// Annotations
obj.SetAnnotation("key", "value");          // set or create
obj.GetAnnotation("key")                    // returns string or null
obj.HasAnnotation("key")                    // returns bool
obj.RemoveAnnotation("key")                 // delete
obj.GetAnnotations()                        // IEnumerable<string> of annotation names
obj.ClearAnnotations()                      // remove all
obj.Annotations                             // AnnotationCollection (indexer access)

// Extended properties
obj.SetExtendedProperty("key", "value", ExtendedPropertyType.String);
obj.SetExtendedProperty("key", jsonStr, ExtendedPropertyType.Json);
obj.GetExtendedProperty("key")              // returns string
obj.HasExtendedProperty("key")              // returns bool
obj.RemoveExtendedProperty("key")           // delete
obj.GetExtendedPropertyType("key")          // String or Json
obj.ExtendedProperties                      // ExtendedPropertyCollection (indexer access)

Setting and reading annotations

Any object implementing (xref:TabularEditor.TOMWrapper.IAnnotationObject) supports annotations. This includes tables, columns, measures, hierarchies, partitions, perspectives, roles, data sources and relationships.

Tag auto-generated measures so a future script can identify and update them:

var m = Model.AllMeasures.First(m => m.Name == "Revenue");
m.SetAnnotation("GeneratedBy", "DateTableScript");
m.SetAnnotation("Owner", "Finance Team");

// Read it back
var owner = m.GetAnnotation("Owner");          // "Finance Team"
var missing = m.GetAnnotation("NoKey");        // null

Later, retrieve all measures tagged by that script:

var autoGenerated = Model.AllMeasures.Where(m => m.GetAnnotation("GeneratedBy") == "DateTableScript");

Checking and removing annotations

Use HasAnnotation() to gate logic on the presence of a tag:

// Skip measures that are flagged for manual review
if (m.HasAnnotation("NeedsReview")) return;

Use RemoveAnnotation() to clean up obsolete keys across the model:

// Remove a deprecated annotation key from all measures that still carry it
Model.AllMeasures
    .Where(m => m.HasAnnotation("LegacyTag"))
    .ForEach(m => m.RemoveAnnotation("LegacyTag"));

Iterating all annotations on an object

GetAnnotations() returns the annotation names. Use GetAnnotation(name) to retrieve values.

foreach (var name in m.GetAnnotations())
{
    var value = m.GetAnnotation(name);
    Info($"{name} = {value}");
}

Using the Annotations collection indexer

The Annotations property provides indexer access as an alternative to the method-based API.

m.Annotations["key"] = "value";        // set
var val = m.Annotations["key"];            // get

Bulk annotation operations

Tag or untag objects across the model.

// Tag all hidden measures
Model.AllMeasures
    .Where(m => m.IsHidden)
    .ForEach(m => m.SetAnnotation("ReviewStatus", "Hidden"));

// Migrate an annotation key from OldKey to NewKey
Model.AllMeasures
    .Where(m => m.HasAnnotation("OldKey"))
    .ForEach(m => {
        m.SetAnnotation("NewKey", m.GetAnnotation("OldKey"));
        m.RemoveAnnotation("OldKey");
    });

Extended properties

Extended properties work similarly to annotations but support a typed ExtendedPropertyType of either String or Json.

// Store a JSON extended property (e.g., field parameter metadata)
var table = Model.Tables["Parameter"];
var json = "{\"version\":3,\"values\":[[\"Revenue\"],[\"Cost\"]]}";
table.SetExtendedProperty("ParameterMetadata", json, ExtendedPropertyType.Json);

// Read back
var value = table.GetExtendedProperty("ParameterMetadata");
var type = table.GetExtendedPropertyType("ParameterMetadata"); // ExtendedPropertyType.Json

// Indexer access
table.ExtendedProperties["key"] = "value";
var val = table.ExtendedProperties["key"];

Dynamic LINQ equivalent

In BPA rule expressions, annotation methods are called directly on the object in context.

C# script Dynamic LINQ (BPA)
m.GetAnnotation("key") == "value" GetAnnotation("key") = "value"
m.HasAnnotation("key") HasAnnotation("key")
m.GetAnnotation("key") != null GetAnnotation("key") != null
m.GetAnnotationsCount() > 0 GetAnnotationsCount() > 0

See also