{"id":1293,"date":"2026-08-04T15:53:14","date_gmt":"2026-08-04T14:53:14","guid":{"rendered":"https:\/\/knitteddata.com\/?p=1293"},"modified":"2026-08-11T11:25:04","modified_gmt":"2026-08-11T10:25:04","slug":"knit-with-c-scripting-in-tabular-editor","status":"publish","type":"post","link":"https:\/\/knitteddata.com\/index.php\/2026\/08\/04\/knit-with-c-scripting-in-tabular-editor\/","title":{"rendered":"Knit with C#: Scripting in Tabular Editor"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">On my blog, you can already find a post where I show how to set everything up for scripting in Tabular Editor and explain how to work with the model. If you haven\u2019t read it yet, click <a href=\"https:\/\/knitteddata.com\/index.php\/2026\/02\/23\/cast-on-with-c-initial-setup-for-scripting-in-tabular-editor\/\">HERE<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In this post, I want to share how I use scripting in Power BI to make my life easier.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I like to use scripts to keep my semantic models consistent. They help me make sure that models follow the same structure, naming conventions are applied across different projects, and everyone who uses my models for reporting or ad hoc analysis can easily find what they need.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Because once you get used to a clean structure, you really do not want to hand-knit every measure one by one anymore.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Creating measures automatically from columns<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The first thing I often use scripts for is creating measures automatically from columns.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Most of the time, I add some information to the column names so I can quickly identify which columns should become measures. In this example, I added the prefix <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">KPI <\/mark>to the columns in the <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">Sales <\/mark>table that I want to turn into measures.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"647\" src=\"https:\/\/knitteddata.com\/wp-content\/uploads\/2026\/08\/Screenshot-2026-08-04-161340-1024x647.png\" alt=\"\" class=\"wp-image-1294\" srcset=\"https:\/\/knitteddata.com\/wp-content\/uploads\/2026\/08\/Screenshot-2026-08-04-161340-1024x647.png 1024w, https:\/\/knitteddata.com\/wp-content\/uploads\/2026\/08\/Screenshot-2026-08-04-161340-300x190.png 300w, https:\/\/knitteddata.com\/wp-content\/uploads\/2026\/08\/Screenshot-2026-08-04-161340-768x485.png 768w, https:\/\/knitteddata.com\/wp-content\/uploads\/2026\/08\/Screenshot-2026-08-04-161340.png 1464w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now let\u2019s open Tabular Editor, go to the <strong>C# Script<\/strong> tab, and paste the following code.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The script will go through all columns in the <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">Sales <\/mark>table and look for columns that contain <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">KPI <\/mark>. For each of those columns, it will create a measure with a simple <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">SUM()<\/mark> expression.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For that, I use the <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">.AddMeasure(Name, DAX Expression, DisplayFolder)<\/mark> function, which is available for every table in the model.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run the code with the green arrow, and remember to save your changes with the disk icon. Otherwise, when you go back to Power BI Desktop, the measures will not be there.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Table where I want to save my measures\nvar MeasuresTable = Model.Tables&#91;\"_Measures\"];\n\n\/\/ Sales table where I can find columns that I want to turn into measures\nvar kpiSourceTable = Model.Tables&#91;\"Sales\"];\n\n\/\/ Loop through all columns in the Sales table\nforeach(var column in kpiSourceTable.Columns)\n{\n    \/\/ Condition to only create measures for columns containing \"KPI \"\n    if (column.Name.Contains(\"KPI \"))\n    {\n        \/\/ Using the .AddMeasure() function\n        MeasuresTable.AddMeasure(\n            column.Name,                                  \/\/ Name\n            \/\/ column.DaxObjectFullName returns the TableName&#91;ColumnName] combination.\n            \"SUM(\" + column.DaxObjectFullName + \")\",      \/\/ DAX expression\n            \"Generated from Columns\"                      \/\/ Display folder\n        );\n    }\n}<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-video\"><video height=\"1080\" style=\"aspect-ratio: 1920 \/ 1080;\" width=\"1920\" autoplay controls muted src=\"https:\/\/knitteddata.com\/wp-content\/uploads\/2026\/08\/Video-Project-25.mp4\" playsinline><\/video><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">One important thing to remember: if you run the script again, it will not overwrite the existing measures. It will add new ones.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So to avoid duplicates, I usually clean up the measures before creating them again. You can delete all generated measures, or you can be more specific and delete only measures from a certain display folder.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In this example, I have a display folder named <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">TO DELETE<\/mark>. I know, very creative.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The following script loops through the <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">_Measures<\/mark> table and deletes all measures where the display folder contains <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">TO DELETE<\/mark>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Again, do not forget to save your changes.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>var MeasuresTable = Model.Tables&#91;\"_Measures\"];\n\nforeach(var m in MeasuresTable.Measures.Where(m =&gt; m.DisplayFolder.Contains(\"TO DELETE\")).ToList())\n{\n    m.Delete();\n}<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-video\"><video height=\"1080\" style=\"aspect-ratio: 1920 \/ 1080;\" width=\"1920\" autoplay controls muted src=\"https:\/\/knitteddata.com\/wp-content\/uploads\/2026\/08\/Video-Project-26.mp4\" playsinline><\/video><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Creating time intelligence measures<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In the next example, I will iterate through the measures we created and generate time intelligence measures based on them.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here I will create:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">AC <\/mark>for actuals, which simply repeats the base measure<\/li>\n\n\n\n<li><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">PY <\/mark>for previous year, using <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">SAMEPERIODLASTYEAR<\/mark><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">You can use the same approach to create any kind of measure you want. You could also use UDFs for this, but that is a topic for another post.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When I create new measures based on existing measures, I also like to clean up the names and remove prefixes or suffixes like <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">KPI <\/mark>or <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">BASE<\/mark>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>var MeasuresTable = Model.Tables&#91;\"_Measures\"];\n\nvar baseMeasures = MeasuresTable.Measures.Where(m =&gt; m.DisplayFolder == \"Generated from BASE\\\\BASE\").ToList();\n\nforeach(var measure in baseMeasures)\n{\n    \/\/ Get the correct measure name\n    var indexOfBase = measure.Name.IndexOf(\"BASE\");\n    var correctMeasureName = measure.Name.Substring(0, indexOfBase);\n   \n    \/\/ Add AC measure\n    var new_AC_measure = MeasuresTable.AddMeasure(\n        correctMeasureName + \" AC\",                      \/\/ Name\n        measure.DaxObjectFullName,                       \/\/ DAX expression\n        \"Generated from BASE\\\\AC\"                        \/\/ Display folder\n    );\n\n    \/\/ Add PY measure\n    var new_PY_measure = MeasuresTable.AddMeasure(\n        correctMeasureName + \" PY\",                                                       \/\/ Name\n        \"CALCULATE(\" + measure.DaxObjectFullName + \", SAMEPERIODLASTYEAR('Date'&#91;Date]))\", \/\/ DAX expression\n        \"Generated from BASE\\\\PY\"                                                        \/\/ Display folder\n    );\n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Applying format strings automatically<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The idea of adding extra information to names can be used for many things. One example I use quite often is formatting.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I often add information like <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">INT<\/mark>, <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">EUR<\/mark>, <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">PRC<\/mark>, or <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">DEC <\/mark>to a measure name, so I know how the measure should be formatted.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One thing that is important to understand: formatting and many other measure attributes have to be added <strong>after<\/strong> the measure is created. They are not part of the <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">.AddMeasure()<\/mark> function itself.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">.AddMeasure()<\/mark> you create the measure first by defining its name, DAX expression, and display folder. After the measure exists, you can assign additional attributes to it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That is why in the next script I first create the measure and save it into a variable:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>var new_measure = MeasuresTable.AddMeasure(...)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And only after that, I set the format string:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>new_measure.FormatString = formatString;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The same idea applies to many other properties. First, create or find the measure. Then update its attributes.<br>Then I can use a script to apply the correct format string automatically.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Define all possible format strings for the measures\nDictionary&lt;string, string&gt; formatStrings = new Dictionary&lt;string, string&gt;();\n\nformatStrings.Add(\"INT\", \"#,##0\");\nformatStrings.Add(\"DEC\", \"#,##0.00\");\nformatStrings.Add(\"EUR\", \"#,##0.00 \u20ac\");\nformatStrings.Add(\"PRC\", \"#,##0.0 %\");\nformatStrings.Add(\"\u20ac\", \"#,##0.00 \u20ac\");\nformatStrings.Add(\"%\", \"#,##0.0 %\");\n\nvar formats = new List&lt;string&gt;() { \"INT\", \"DEC\", \"EUR\", \"PRC\", \"\u20ac\", \"%\" };\n\nvar MeasuresTable = Model.Tables&#91;\"_Measures\"];\n\nvar baseMeasures = MeasuresTable.Measures.Where(m =&gt; m.DisplayFolder == \"Generated from BASE\\\\BASE\").ToList();\n\nforeach(var measure in baseMeasures)\n{\n    \/\/ Default format\n    var formatString = \"#,##0.00\";\n\n    \/\/ Find format for current measure\n    foreach (var format in formats)\n    {\n        if(measure.Name.EndsWith(format))\n        {\n            formatString = formatStrings&#91;format];\n            break;\n        }\n    }\n\n    \/\/ Get the correct measure name\n    var indexOfBase = measure.Name.IndexOf(\"BASE\");\n    var correctMeasureName = measure.Name.Substring(0, indexOfBase);\n   \n    \/\/ Add measure\n    var new_measure = MeasuresTable.AddMeasure(\n        correctMeasureName,                              \/\/ Name\n        measure.DaxObjectFullName,                       \/\/ DAX expression\n        \"Change format\"                                  \/\/ Display folder\n    );\n\n    new_measure.FormatString = formatString;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This script checks the ending of each measure name and applies the corresponding format string. For example, a measure ending with <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">EUR <\/mark>will get a currency format, and a measure ending with <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">PRC <\/mark>will get a percentage format.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is a small thing, but when you work with many measures, it saves a lot of clicking.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Final thoughts<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">These are just a few examples of scripts I use quite often when creating semantic models.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I usually try to write scripts in a way that makes them reusable across multiple models.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once you get comfortable with scripting, you can use the same idea for many other repetitive modelling tasks, for example:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>hiding technical columns<\/li>\n\n\n\n<li>setting descriptions<\/li>\n\n\n\n<li>organising display folders<\/li>\n\n\n\n<li>applying naming conventions<\/li>\n\n\n\n<li>changing summarisation settings<\/li>\n\n\n\n<li>creating calculation groups<\/li>\n\n\n\n<li>checking whether required measures exist<\/li>\n\n\n\n<li>adding translations or perspectives<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Of course, every project is different, but many tasks repeat again and again: creating measures, cleaning up naming, applying formatting, organizing display folders, hiding technical columns, and keeping the model tidy.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">And this is exactly where Tabular Editor scripting shines.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of manually fixing the same things model by model, you can create your own little collection of scripts and reuse them whenever you need them.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So next time you find yourself repeating the same boring model cleanup task again and again, maybe it is time to stop hand-knitting and let C# do a few stitches for you.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let me know what scripts you use in your Power BI projects. I am always curious what others automate in their semantic models.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>On my blog, you can already find a post where I show how to set&hellip;<\/p>\n","protected":false},"author":2,"featured_media":1294,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[30],"tags":[38,41,39],"class_list":["post-1293","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-power-bi","tag-c-scripting","tag-power-bi","tag-tabular-editor"],"_links":{"self":[{"href":"https:\/\/knitteddata.com\/index.php\/wp-json\/wp\/v2\/posts\/1293","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/knitteddata.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/knitteddata.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/knitteddata.com\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/knitteddata.com\/index.php\/wp-json\/wp\/v2\/comments?post=1293"}],"version-history":[{"count":4,"href":"https:\/\/knitteddata.com\/index.php\/wp-json\/wp\/v2\/posts\/1293\/revisions"}],"predecessor-version":[{"id":1301,"href":"https:\/\/knitteddata.com\/index.php\/wp-json\/wp\/v2\/posts\/1293\/revisions\/1301"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/knitteddata.com\/index.php\/wp-json\/wp\/v2\/media\/1294"}],"wp:attachment":[{"href":"https:\/\/knitteddata.com\/index.php\/wp-json\/wp\/v2\/media?parent=1293"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/knitteddata.com\/index.php\/wp-json\/wp\/v2\/categories?post=1293"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/knitteddata.com\/index.php\/wp-json\/wp\/v2\/tags?post=1293"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}