You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: content/features/Useful-script-snippets.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,6 +2,7 @@
2
2
uid: useful-script-snippets
3
3
title: Useful script snippets
4
4
author: Daniel Otykier
5
+
updated: 2026-04-10
5
6
applies_to:
6
7
products:
7
8
- product: Tabular Editor 2
@@ -22,7 +23,7 @@ Here's a collection of small script snippets to get you started using the [Advan
22
23
Also, make sure to check out our script library @csharp-script-library, for some more real-life examples of what you can do with the scripting capabilities of Tabular Editor.
23
24
24
25
> [!TIP]
25
-
> For structured, pattern-by-pattern reference material on C# scripting and Dynamic LINQ, see the [Scripting Patterns](/how-tos/scripting-navigate-tom-hierarchy) how-to series. It covers object navigation, type checking, LINQ querying, dependencies, annotations, expressions and more.
26
+
> For structured, pattern-by-pattern reference material on C# scripting and Dynamic LINQ, see the [Scripting Patterns](/how-tos/scripting-navigate-tom-hierarchy) how-to series. For the complete TOM wrapper API, see the @api-index.
`AddMeasure()` creates a new measure on a table. All parameters except the first are optional.
47
+
`AddMeasure()` creates and returns a new `Measure` on a table. The first parameter is the name, the second is a DAX expression and the third is the display folder. All parameters except the first are optional.
48
+
49
+
Capture the returned object in a variable to set additional properties. This pattern is the same across all `Add*` methods.
// Data column -- maps to a source column in the partition query
82
+
vardc=table.AddDataColumn(
83
+
"Region", // name
84
+
"RegionName", // source column name
85
+
"Geography", // display folder
86
+
DataType.String// data type
87
+
);
70
88
```
71
89
90
+
> [!WARNING]
91
+
> Adding a data column does not modify the table's partition query. You must update the M expression or SQL query separately to include a source column that matches the `sourceColumn` parameter.
92
+
72
93
## Adding hierarchies
73
94
74
-
Pass columns as parameters to automatically create levels.
95
+
The `levels` parameter is variadic. Pass any number of columns in a single call to create the corresponding levels automatically.
`AddRelationship()` creates an empty relationship. You must set the columns explicitly.
128
+
`AddRelationship()` creates and returns an empty relationship. You must set the columns explicitly.
129
+
130
+
`FromColumn` is the many (N) side and `ToColumn` is the one (1) side. Tabular Editor does not detect the direction automatically. A useful mnemonic: F for From, F for Fact table (the many side).
131
+
132
+
New relationships default to `CrossFilteringBehavior.OneDirection` and `IsActive = true`. Set these only if you need a different value.
A common pattern: iterate selected columns and create derived measures.
159
+
A common pattern: iterate selected columns and create derived measures. Note the use of `DaxObjectFullName` which returns the fully qualified, properly quoted DAX reference (e.g., `'Sales'[Amount]`) to avoid quoting errors.
130
160
131
161
```csharp
132
162
foreach (varcolinSelected.Columns)
@@ -143,7 +173,7 @@ foreach (var col in Selected.Columns)
143
173
144
174
## Deleting objects
145
175
146
-
Call `Delete()` on any named object to remove it. When deleting inside a loop, always call `.ToList()` first to avoid modifying the collection during iteration.
176
+
Call `Delete()` on any named object to remove it. When modifying a collection in a loop (deleting, adding or moving objects), always call `.ToList()` first to materialize a snapshot.
147
177
148
178
```csharp
149
179
// Delete a single object
@@ -159,14 +189,18 @@ Model.AllMeasures
159
189
## Common pitfalls
160
190
161
191
> [!WARNING]
162
-
> - Always call `.ToList()` before deleting objects in a loop. Without it, modifying the collection during iteration causes an exception.
163
-
> -`AddRelationship()` creates an incomplete relationship. You must assign both `FromColumn` and `ToColumn` before the model validates. Failing to do so results in a validation error.
164
-
> -New objects have default property values. Set `DataType`, `FormatString`, `IsHidden` and other properties explicitly after creation.
165
-
> -`Clone()` copies all metadata including annotations, translations and perspective membership. If you do not want to inherit these, remove them after cloning.
192
+
> - Always call `.ToList()`or `.ToArray()`before modifying objects in a loop. Without it, modifying the collection during iteration causes: `"Collection was modified; enumeration operation may not complete."`
193
+
> -`AddRelationship()` creates an incomplete relationship. You must assign both `FromColumn` and `ToColumn` before the model validates.
194
+
> -`Column` is abstract, but you can access all base properties (`Name`, `DataType`, `FormatString`, `IsHidden`) without casting. Only cast to a subtype for type-specific properties.
195
+
> -`Clone()` copies all metadata including annotations, translations and perspective membership. Remove unwanted metadata after cloning.
166
196
167
197
## See also
168
198
169
199
-@useful-script-snippets
170
200
-@script-create-sum-measures-from-columns
171
201
-@how-to-navigate-tom-hierarchy
172
202
-@how-to-use-selected-object
203
+
- (xref:TabularEditor.TOMWrapper.Measure) -- Measure API reference
204
+
- (xref:TabularEditor.TOMWrapper.Column) -- Column API reference
205
+
- (xref:TabularEditor.TOMWrapper.Hierarchy) -- Hierarchy API reference
206
+
- (xref:TabularEditor.TOMWrapper.SingleColumnRelationship) -- Relationship API reference
Copy file name to clipboardExpand all lines: content/how-tos/scripting-check-object-types.md
+32-67Lines changed: 32 additions & 67 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
uid: how-to-check-object-types
3
3
title: How to Check Object Types
4
4
author: Morten Lønskov
5
-
updated: 2026-04-09
5
+
updated: 2026-04-10
6
6
applies_to:
7
7
products:
8
8
- product: Tabular Editor 2
@@ -12,44 +12,43 @@ applies_to:
12
12
---
13
13
# How to Check Object Types
14
14
15
-
The TOM hierarchy uses inheritance. `Column` is an abstract base with subtypes `DataColumn`, `CalculatedColumn` and `CalculatedTableColumn`. `Table` has the subtype `CalculationGroupTable`. This article shows how to test and filter by type in C# scripts and Dynamic LINQ.
15
+
The TOM hierarchy uses inheritance. `Column` is an abstract base with subtypes `DataColumn`, `CalculatedColumn` and `CalculatedTableColumn`. `Table` has subtypes `CalculatedTable` and `CalculationGroupTable`. Use the base type when working with shared properties like `Name`, `Description`, `IsHidden`, `FormatString` or `DisplayFolder`. Cast to a concrete subtype when you need type-specific properties, such as `Expression` on `CalculatedColumn` or `SourceColumn` on `DataColumn`.
16
16
17
17
## Quick reference
18
18
19
19
```csharp
20
-
// Pattern matching (preferred)
20
+
// Pattern matching -- checks type AND casts in one step
21
21
if (colisCalculatedColumncc)
22
-
Info(cc.Expression);
22
+
Info(cc.Expression);// Expression is only on CalculatedColumn, not base Column
// Runtime type name (use only for display/logging, not for logic)
29
29
stringtypeName=obj.GetType().Name; // "DataColumn", "Measure", etc.
30
-
31
-
// Null-safe cast
32
-
vardc=colasDataColumn;
33
-
if (dc!=null) { /* use dc */ }
34
30
```
35
31
32
+
> [!NOTE]
33
+
> Pattern matching with variable declaration (`col is CalculatedColumn cc`) requires the Roslyn compiler in Tabular Editor 2. Enable it under **File > Preferences > General > Use Roslyn compiler**. Tabular Editor 3 supports this by default.
34
+
36
35
## Type hierarchy
37
36
38
37
The key inheritance relationships in the TOM wrapper:
`OfType<T>()`filters and casts in one step. Prefer it over `Where(x => x is T)`.
48
+
`OfType<T>()`works on any collection and returns a filtered sequence containing only items that are the specified type. It returns an empty sequence if no items match.
50
49
51
50
```csharp
52
-
// All calculated columns in the model
51
+
// All calculated columns in the model (empty if model has none)
Use C# pattern matching to test and cast in a single expression.
68
-
69
-
```csharp
70
-
foreach (varcolinModel.AllColumns)
71
-
{
72
-
if (colisCalculatedColumncc)
73
-
Info($"{cc.Name}: {cc.Expression}");
74
-
elseif (colisDataColumndc)
75
-
Info($"{dc.Name}: data column in {dc.Table.Name}");
76
-
}
77
-
```
78
-
79
-
## Checking ObjectType enum
66
+
Pattern matching does two things: it checks whether a value is a given type and optionally casts it into a new variable. The form `x is Type xx` asks "is `x` of type `Type`?" and, if true, gives you `xx` as a variable of that exact type.
80
67
81
-
Every TOM object has an `ObjectType` property that returns an enum value. This identifies the base type, not the subtype.
68
+
This is equivalent to:
82
69
83
70
```csharp
84
-
foreach (varobjinSelected.Objects)
71
+
if (colisCalculatedColumn)
85
72
{
86
-
switch (obj.ObjectType)
87
-
{
88
-
caseObjectType.Measure: /* ... */break;
89
-
caseObjectType.Column: /* ... */break;
90
-
caseObjectType.Table: /* ... */break;
91
-
caseObjectType.Hierarchy: /* ... */break;
92
-
}
73
+
varcc= (CalculatedColumn)col; // explicit cast
74
+
// use cc...
93
75
}
94
76
```
95
77
96
-
> [!WARNING]
97
-
> `ObjectType` does not distinguish subtypes. A `CalculatedColumn` and a `DataColumn` both return `ObjectType.Column`. Use `is` or `OfType<T>()` when you need subtype-level checks.
98
-
99
-
## Checking partition source type
100
-
101
-
For partitions, use the `SourceType` property to distinguish between storage modes without type-casting.
78
+
If you only need the boolean check, use `x is Type` without the variable. If you also need subtype-specific properties, use `x is Type xx`.
102
79
103
80
```csharp
104
-
foreach (varpinModel.AllPartitions)
81
+
foreach (varcolinModel.AllColumns)
105
82
{
106
-
switch (p.SourceType)
107
-
{
108
-
casePartitionSourceType.M: /* Power Query */break;
// Expression is only available on CalculatedColumn, not the base Column type
84
+
if (colisCalculatedColumncc)
85
+
Info($"{cc.Name}: {cc.Expression}");
86
+
elseif (colisDataColumndc)
87
+
Info($"{dc.Name}: data column in {dc.Table.Name}");
113
88
}
114
89
```
115
90
116
91
## Dynamic LINQ equivalent
117
92
118
-
In BPA rules, type filtering works differently. Set the rule's **Applies to** scope to target a specific object type. Within the expression, use `ObjectTypeName` for the base type name as a string.
119
-
120
-
```
121
-
// BPA expression context: the rule "Applies to" determines the object type
122
-
// No C#-style type casting is available in Dynamic LINQ
123
-
124
-
// Check the object type name (rarely needed since scope handles this)
125
-
ObjectTypeName = "Measure"
126
-
ObjectTypeName = "Column"
127
-
```
128
-
129
-
For subtypes like calculated columns, set the BPA rule scope to **Calculated Columns** rather than trying to filter by type in the expression.
93
+
In BPA rules, type filtering is handled by the rule's **Applies to** scope. Set it to the target object type (e.g., **Calculated Columns**) rather than filtering by type in the expression. No C#-style type casting is available in Dynamic LINQ.
130
94
131
95
## Common pitfalls
132
96
133
97
> [!IMPORTANT]
134
-
> -`Column` is abstract. You cannot create an instance of `Column` directly. Use `table.AddDataColumn()` or `table.AddCalculatedColumn()` on regular tables. `AddCalculatedTableColumn()` is only available on `CalculatedTable`.
98
+
> -`Column` is abstract, but you can access all properties defined on the base type (`Name`, `DataType`, `FormatString`, `IsHidden`, `Description`, `DisplayFolder`) without casting. Only cast to a subtype when you need subtype-specific properties like `Expression`on `CalculatedColumn`.
135
99
> -`OfType<T>()` both filters and casts. `Where(x => x is T)` only filters, leaving you with the base type. Prefer `OfType<T>()` when you need access to subtype properties.
136
-
> -`ObjectType` and `SourceType` are base-level checks. For subtype-specific logic (e.g., accessing `Expression`on a `CalculatedColumn`), use `is` or `OfType<T>()`.
100
+
> -Calculated table columns are managed automatically. Edit the calculated table's `Expression`to add or change columns. You cannot add them directly.
0 commit comments