Table of Contents

How Dynamic LINQ Differs from C# LINQ

C# scripts use standard C# LINQ with lambda expressions. Best Practice Analyzer (BPA) rules and Explorer tree filters use Dynamic LINQ, a string-based expression language with different syntax. This article is a translation guide between the two.

Where each is used

Context Syntax
C# scripts and macros C# LINQ
BPA rule expressions Dynamic LINQ
BPA fix expressions Dynamic LINQ (with it. prefix for assignments)
TOM Explorer tree filter (: prefix)* Dynamic LINQ

* Tabular Editor 2 only.

Syntax comparison

In Dynamic LINQ, the object is implicit -- there is no lambda parameter like m. or c.. In BPA, the surrounding context is the Applies to scope setting, which determines which object type the expression evaluates against.

Concept C# LINQ (scripts) Dynamic LINQ (BPA / filter)
Boolean AND && and
Boolean OR \|\| or
Boolean NOT ! not
Equals == =
Not equals != != or <>
Greater/less >, <, >=, <= >, <, >=, <=
String contains m.Name.Contains("Sales") Name.Contains("Sales")
String starts with m.Name.StartsWith("Sum") Name.StartsWith("Sum")
String ends with m.Name.EndsWith("YTD") Name.EndsWith("YTD")
Null/empty check string.IsNullOrEmpty(m.Description) String.IsNullOrEmpty(Description)
Whitespace check string.IsNullOrWhiteSpace(m.Description) String.IsNullOrWhitespace(Description)
Regex match Regex.IsMatch(m.Name, "pattern") RegEx.IsMatch(Name, "pattern")

Enum comparison

C# uses typed enum values. Dynamic LINQ uses string representations.

C# LINQ Dynamic LINQ
c.DataType == DataType.String DataType = "String"
p.SourceType == PartitionSourceType.M SourceType = "M"
p.Mode == ModeType.DirectLake Mode = "DirectLake"
r.CrossFilteringBehavior == CrossFilteringBehavior.BothDirections CrossFilteringBehavior = "BothDirections"

Lambda expressions vs implicit context

C# LINQ uses explicit lambda parameters. Dynamic LINQ evaluates properties on an implicit it context object.

// C# LINQ: explicit lambda parameter
Model.AllMeasures.Where(m => m.IsHidden && m.Description == "");
// Dynamic LINQ: implicit "it" -- properties are accessed directly
IsHidden and Description = ""

Parent object navigation

Both use dot notation, but C# requires the lambda parameter.

// C# LINQ
Model.AllMeasures.Where(m => m.Table.IsHidden);
// Dynamic LINQ
Table.IsHidden

Collection methods

C# LINQ uses lambdas inside collection methods. Dynamic LINQ uses implicit context within collection methods, with outerIt to reference the parent object.

// C# LINQ: count columns with no description
Model.Tables.Where(t => t.Columns.Count(c => c.Description == "") > 5);
// Dynamic LINQ: same logic
Columns.Count(Description = "") > 5

The outerIt keyword

Inside a nested collection method in Dynamic LINQ, it refers to the inner object (e.g., a column). Use outerIt to reference the outer object (e.g., the table).

// BPA rule on Tables: find tables where any column name matches the table name
Columns.Any(Name = outerIt.Name)

In C#, the outer lambda parameter t remains in scope throughout the inner lambda body. The inner lambda c => c.Name == t.Name can reference t directly because it is captured by closure.

// C# equivalent -- t is accessible inside the inner lambda via closure
Model.Tables.Where(t => t.Columns.Any(c => c.Name == t.Name));

Type filtering

C# uses OfType<T>() or is. In BPA, the rule's Applies to scope handles type filtering. You do not need type checks in the expression itself.

C# LINQ Dynamic LINQ approach
Model.AllColumns.OfType<CalculatedColumn>() Set BPA rule scope to Calculated Columns
Model.Tables.OfType<CalculationGroupTable>() Set BPA rule scope to Calculation Group Tables

Dependency properties

These work identically in both syntaxes, but Dynamic LINQ omits the object prefix.

C# LINQ Dynamic LINQ
m.ReferencedBy.Count == 0 ReferencedBy.Count = 0
m.DependsOn.Any() DependsOn.Any()
c.UsedInRelationships.Any() UsedInRelationships.Any()
c.ReferencedBy.AnyVisible ReferencedBy.AnyVisible

Annotation methods

// C# LINQ
Model.AllMeasures.Where(m => m.HasAnnotation("AUTOGEN"));
// Dynamic LINQ
HasAnnotation("AUTOGEN")
C# LINQ Dynamic LINQ
m.GetAnnotation("key") == "value" GetAnnotation("key") = "value"
m.HasAnnotation("key") HasAnnotation("key")

Perspective and translation indexers

// C# LINQ
Model.AllMeasures.Where(m => m.InPerspective["Sales"]);
// Dynamic LINQ
InPerspective["Sales"]
C# LINQ Dynamic LINQ
m.InPerspective["Sales"] InPerspective["Sales"]
!m.InPerspective["Sales"] not InPerspective["Sales"]
string.IsNullOrEmpty(m.TranslatedNames["da-DK"]) String.IsNullOrEmpty(TranslatedNames["da-DK"])

BPA fix expressions

Fix expressions use it. as the assignment target. The it refers to the specific object that violated the rule -- the same object highlighted in the BPA results list.

For example, given a BPA rule with expression IsHidden and String.IsNullOrWhitespace(Description) applied to Measures, each measure that matches appears in the BPA results. When you apply the fix, it refers to that specific measure:

// Set the description on the violating measure
it.Description = "TODO: Add description"

// Unhide the violating object
it.IsHidden = false

While fix expressions have no direct C# LINQ equivalent, you can achieve the same result in a script:

foreach (var m in Model.AllMeasures.Where(m => m.IsHidden && string.IsNullOrWhiteSpace(m.Description)))
{
    m.Description = "TODO: Add description";
}

Complete example: same rule in both syntaxes

Goal: Find measures that are hidden, have no references and have no description.

C# script:

var unused = Model.AllMeasures
    .Where(m => m.IsHidden
        && m.ReferencedBy.Count == 0
        && string.IsNullOrWhiteSpace(m.Description));

foreach (var m in unused)
    Info(m.DaxObjectFullName);

BPA rule expression (applies to Measures):

IsHidden and ReferencedBy.Count = 0 and String.IsNullOrWhitespace(Description)

See also