├── Solution
├── Exercise04
│ ├── PBI-Tool
│ │ ├── Query.dat
│ │ ├── Servers.dat
│ │ ├── PBI-Tool.csproj
│ │ ├── .vscode
│ │ │ ├── launch.json
│ │ │ └── tasks.json
│ │ └── Program.cs
│ └── DAXFormatter.pbitool.json
├── Exercise03
│ └── PBI-Tool
│ │ ├── QueryResuts.csv
│ │ ├── PBI-Tool.csproj
│ │ ├── .vscode
│ │ ├── launch.json
│ │ └── tasks.json
│ │ └── Program.cs
├── Exercise01
│ └── PBI-Tool
│ │ ├── PBI-Tool.csproj
│ │ ├── .vscode
│ │ ├── launch.json
│ │ └── tasks.json
│ │ └── Program.cs
└── Exercise02
│ └── PBI-Tool
│ ├── PBI-Tool.csproj
│ ├── .vscode
│ ├── launch.json
│ └── tasks.json
│ └── Program.cs
├── Tutorial.docx
├── Tutorial.pdf
├── TOM_Figures.pptx
├── Report Templates.pptx
├── PBIX
└── Wingtip Sales Model.pbix
├── Demos
├── Learning-TOM
│ ├── Learning-TOM
│ │ ├── DAX
│ │ │ ├── QueryGetSalesByState.dax
│ │ │ ├── CalculatedTable-Calendar.dax
│ │ │ ├── CalculatedColumn-AgeGroup.dax
│ │ │ ├── CalculatedColumn-SalesRegionSort.dax
│ │ │ └── CalculatedColumn-SalesRegion.dax
│ │ ├── M
│ │ │ ├── SalesQuery.m
│ │ │ ├── ProductQuery.m
│ │ │ └── CustomersQuery.m
│ │ ├── Program.cs
│ │ ├── Learning-TOM.csproj
│ │ ├── Properties
│ │ │ ├── Resources.resx
│ │ │ └── Resources.Designer.cs
│ │ └── DatasetManager.cs
│ └── Learning-TOM.sln
├── DaxFormatterExternalTool
│ ├── DaxFormatterExternalTool.csproj
│ ├── Program.cs
│ ├── .vscode
│ │ ├── launch.json
│ │ └── tasks.json
│ └── DaxFormatter.cs
└── DAXFormatter.pbitool.json
├── README.md
└── StarterFiles
├── Exercise01-Starter-Program.cs
├── Exercise01-Final-Program.cs
├── Exercise02-Program.cs
├── Exercise03-Program.cs
├── pbitools.pbitool.json
└── Exercise04-Program.cs
/Solution/Exercise04/PBI-Tool/Query.dat:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Solution/Exercise04/PBI-Tool/Servers.dat:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Tutorial.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PowerBiDevCamp/Tabular-Object-Model-Tutorial/HEAD/Tutorial.docx
--------------------------------------------------------------------------------
/Tutorial.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PowerBiDevCamp/Tabular-Object-Model-Tutorial/HEAD/Tutorial.pdf
--------------------------------------------------------------------------------
/TOM_Figures.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PowerBiDevCamp/Tabular-Object-Model-Tutorial/HEAD/TOM_Figures.pptx
--------------------------------------------------------------------------------
/Report Templates.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PowerBiDevCamp/Tabular-Object-Model-Tutorial/HEAD/Report Templates.pptx
--------------------------------------------------------------------------------
/PBIX/Wingtip Sales Model.pbix:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PowerBiDevCamp/Tabular-Object-Model-Tutorial/HEAD/PBIX/Wingtip Sales Model.pbix
--------------------------------------------------------------------------------
/Demos/Learning-TOM/Learning-TOM/DAX/QueryGetSalesByState.dax:
--------------------------------------------------------------------------------
1 | EVALUATE
2 | SUMMARIZECOLUMNS(
3 | Customers[State],
4 | "Sales Revenue", SUM(Sales[SalesAmount]),
5 | "Units Sold", SUM(Sales[Quantity])
6 | )
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tabular-Object-Model-Tutorial
2 | This repo provides a tutorial and sample code programming datasets using the Tabular Object Model with Power BI Desktop and the Power BI Service via the XMLA endpoint.
3 |
--------------------------------------------------------------------------------
/Demos/Learning-TOM/Learning-TOM/DAX/CalculatedTable-Calendar.dax:
--------------------------------------------------------------------------------
1 | Var CalenderStart = Date(Year(Min(Sales[InvoiceDate])) , 1, 1)
2 | Var CalendarEnd = Date(Year(MAX(Sales[InvoiceDate])), 12, 31)
3 | Return
4 | CALENDAR(CalenderStart, CalendarEnd)
5 |
--------------------------------------------------------------------------------
/Solution/Exercise03/PBI-Tool/QueryResuts.csv:
--------------------------------------------------------------------------------
1 | Customers[State],[Sales Revenue],[Units Sold]
2 | CA,5255912.41,623260
3 | OR,1849876.53,214463
4 | WA,1785527.07,182481
5 | NM,843053.8,125201
6 | CO,1089855.17,169969
7 | AZ,1515934.54,223647
8 | UT,393728.68,59104
9 |
--------------------------------------------------------------------------------
/Demos/Learning-TOM/Learning-TOM/DAX/CalculatedColumn-AgeGroup.dax:
--------------------------------------------------------------------------------
1 | SWITCH (
2 | TRUE(),
3 | [Age] >= 65, "65 and over",
4 | [Age] >= 50, "50 to 64",
5 | [Age] >= 40, "40 to 49",
6 | [Age] >= 30, "30 to 39",
7 | [Age] >= 18, "18 to 29",
8 | [Age] < 18, "Under 18"
9 | )
--------------------------------------------------------------------------------
/Solution/Exercise01/PBI-Tool/PBI-Tool.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net5.0
6 | PBI_Tool
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Solution/Exercise02/PBI-Tool/PBI-Tool.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net5.0
6 | PBI_Tool
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Demos/Learning-TOM/Learning-TOM/DAX/CalculatedColumn-SalesRegionSort.dax:
--------------------------------------------------------------------------------
1 | SWITCH(
2 | TRUE(),
3 | [State] in {"AK", "AZ", "CA", "CO", "HI", "MT", "NM", "NV", "OR", "UT", "WA"},
4 | 1,
5 | [State] in {"AL", "AR", "IA", "ID", "IL", "IN", "KS", "KY", "LA", "MI", "MN", "MO", "MS", "ND", "NE", "OH", "OK", "SD", "TN", "TX", "WI", "WV", "WY"},
6 | 2,
7 | [State] in {"CT", "DE", "FL", "GA", "MA", "MD", "ME", "NC", "NH", "NJ", "NY", "PA", "RI", "SC", "VA", "VT"},
8 | 3
9 | )
--------------------------------------------------------------------------------
/Demos/DaxFormatterExternalTool/DaxFormatterExternalTool.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net5.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Demos/Learning-TOM/Learning-TOM/DAX/CalculatedColumn-SalesRegion.dax:
--------------------------------------------------------------------------------
1 | SWITCH(
2 | TRUE(),
3 | [State] in {"AK", "AZ", "CA", "CO", "HI", "MT", "NM", "NV", "OR", "UT", "WA"},
4 | "Western Region",
5 | [State] in {"AL", "AR", "IA", "ID", "IL", "IN", "KS", "KY", "LA", "MI", "MN", "MO", "MS", "ND", "NE", "OH", "OK", "SD", "TN", "TX", "WI", "WV", "WY"},
6 | "Central Region",
7 | [State] in {"CT", "DE", "FL", "GA", "MA", "MD", "ME", "NC", "NH", "NJ", "NY", "PA", "RI", "SC", "VA", "VT"},
8 | "Eastern Region"
9 | )
--------------------------------------------------------------------------------
/Demos/Learning-TOM/Learning-TOM/M/SalesQuery.m:
--------------------------------------------------------------------------------
1 | let
2 | Source = Sql.Database("devcamp.database.windows.net", "WingtipSalesDB"),
3 | dbo_InvoiceDetails = Source{[Schema="dbo",Item="InvoiceDetails"]}[Data],
4 | ExpandedInvoices = Table.ExpandRecordColumn(dbo_InvoiceDetails, "Invoices", {"InvoiceDate", "CustomerId"}, {"InvoiceDate", "CustomerId"}),
5 | RemovedColumns = Table.RemoveColumns(ExpandedInvoices,{"Products"}),
6 | ChangedType = Table.TransformColumnTypes(RemovedColumns,{{"InvoiceDate", type date}, {"SalesAmount", Currency.Type}})
7 | in
8 | ChangedType
--------------------------------------------------------------------------------
/Solution/Exercise03/PBI-Tool/PBI-Tool.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net5.0
6 | PBI_Tool
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Solution/Exercise04/PBI-Tool/PBI-Tool.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net5.0
6 | PBI_Tool
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Demos/DaxFormatterExternalTool/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AnalysisServices.Tabular;
2 |
3 | namespace DaxFormatterExternalTool {
4 |
5 | class Program {
6 |
7 | static void Main(string[] args) {
8 |
9 | string connectString = args.Length >= 1 ? args[0] : "localhost:53610";
10 |
11 | Server server = new Server();
12 | server.Connect(connectString);
13 |
14 | Model model = server.Databases[0].Model;
15 |
16 | DaxFormatter.FormatDaxForModel(model);
17 |
18 | }
19 |
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/StarterFiles/Exercise01-Starter-Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AnalysisServices.Tabular;
3 |
4 | class Program {
5 |
6 | const string connectString = "localhost:50000"; // update with port number on your machine
7 |
8 | static void Main(string[] args) {
9 |
10 | Server server = new Server();
11 | server.Connect(connectString);
12 |
13 | Model model = server.Databases[0].Model;
14 |
15 | foreach(Table table in model.Tables) {
16 | Console.WriteLine($"Table : {table.Name}");
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Demos/Learning-TOM/Learning-TOM/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AnalysisServices.Tabular;
3 |
4 | namespace Learning_TOM {
5 |
6 | class Program {
7 |
8 | static void Main() {
9 |
10 | string DatabaseName = "Demo Dataset 1";
11 |
12 | DatasetManager.ConnectToPowerBIAsUser();
13 | Database database = DatasetManager.CreateDatabase(DatabaseName);
14 | DatasetManager.CreateWingtipSalesModel(database);
15 |
16 | //string newDatabaseName = "My Cloned Dataset Copy";
17 | //DatasetManager.CopyDatabase(DatabaseName, newDatabaseName);
18 | //database = DatasetManager.CreateDatabase(newDatabaseName);
19 |
20 | }
21 |
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Demos/Learning-TOM/Learning-TOM/M/ProductQuery.m:
--------------------------------------------------------------------------------
1 | let
2 | Source = Sql.Database("devcamp.database.windows.net", "WingtipSalesDB"),
3 | dbo_Products = Source{[Schema="dbo",Item="Products"]}[Data],
4 | RemovedOtherColumns = Table.SelectColumns(dbo_Products,{"ProductId", "Title", "Description", "ProductCategory", "ProductImageUrl"}),
5 | RenamedColumns = Table.RenameColumns(RemovedOtherColumns,{{"Title", "Product"}}),
6 | SplitColumnByDelimiter = Table.SplitColumn(RenamedColumns, "ProductCategory", Splitter.SplitTextByDelimiter(" > ", QuoteStyle.Csv), {"ProductCategory.1", "ProductCategory.2"}),
7 | ChangedType = Table.TransformColumnTypes(SplitColumnByDelimiter,{{"ProductCategory.1", type text}, {"ProductCategory.2", type text}}),
8 | RenamedColumns1 = Table.RenameColumns(ChangedType,{{"ProductCategory.1", "Category"}, {"ProductCategory.2", "Subcategory"}})
9 | in
10 | RenamedColumns1
--------------------------------------------------------------------------------
/Demos/DaxFormatterExternalTool/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Launch (console)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | "program": "${workspaceFolder}/bin/Debug/net5.0/DaxFormatterExternalTool.dll",
13 | "args": [],
14 | "cwd": "${workspaceFolder}",
15 | "console": "externalTerminal",
16 | "stopAtEntry": false
17 | },
18 | {
19 | "name": ".NET Core Attach",
20 | "type": "coreclr",
21 | "request": "attach",
22 | "processId": "${command:pickProcess}"
23 | }
24 | ]
25 | }
--------------------------------------------------------------------------------
/Solution/Exercise01/PBI-Tool/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Launch (console)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | "program": "${workspaceFolder}/bin/Debug/net5.0/PBI-Tool.dll",
13 | "args": [],
14 | "cwd": "${workspaceFolder}",
15 | "console": "internalConsole",
16 | "stopAtEntry": false,
17 | "logging": { "moduleLoad": false }
18 | },
19 | {
20 | "name": ".NET Core Attach",
21 | "type": "coreclr",
22 | "request": "attach",
23 | "processId": "${command:pickProcess}"
24 | }
25 | ]
26 | }
--------------------------------------------------------------------------------
/Solution/Exercise02/PBI-Tool/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Launch (console)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | "program": "${workspaceFolder}/bin/Debug/net5.0/PBI-Tool.dll",
13 | "args": [],
14 | "cwd": "${workspaceFolder}",
15 | "console": "internalConsole",
16 | "stopAtEntry": false,
17 | "logging": { "moduleLoad": false }
18 | },
19 | {
20 | "name": ".NET Core Attach",
21 | "type": "coreclr",
22 | "request": "attach",
23 | "processId": "${command:pickProcess}"
24 | }
25 | ]
26 | }
--------------------------------------------------------------------------------
/Solution/Exercise03/PBI-Tool/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Launch (console)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | "program": "${workspaceFolder}/bin/Debug/net5.0/PBI-Tool.dll",
13 | "args": [],
14 | "cwd": "${workspaceFolder}",
15 | "console": "internalConsole",
16 | "stopAtEntry": false,
17 | "logging": { "moduleLoad": false }
18 | },
19 | {
20 | "name": ".NET Core Attach",
21 | "type": "coreclr",
22 | "request": "attach",
23 | "processId": "${command:pickProcess}"
24 | }
25 | ]
26 | }
--------------------------------------------------------------------------------
/Solution/Exercise04/PBI-Tool/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Launch (console)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | "program": "${workspaceFolder}/bin/Debug/net5.0/PBI-Tool.dll",
13 | "args": [],
14 | "cwd": "${workspaceFolder}",
15 | "console": "externalTerminal",
16 | "stopAtEntry": false,
17 | "logging": { "moduleLoad": false }
18 | },
19 | {
20 | "name": ".NET Core Attach",
21 | "type": "coreclr",
22 | "request": "attach",
23 | "processId": "${command:pickProcess}"
24 | }
25 | ]
26 | }
--------------------------------------------------------------------------------
/Demos/Learning-TOM/Learning-TOM/Learning-TOM.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 | Learning_TOM
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | True
17 | True
18 | Resources.resx
19 |
20 |
21 |
22 |
23 |
24 | ResXFileCodeGenerator
25 | Resources.Designer.cs
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Solution/Exercise01/PBI-Tool/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AnalysisServices.Tabular;
3 |
4 | class Program {
5 |
6 | const string connectString = "localhost:57308"; // update with port number on your machine
7 |
8 | static void Main(string[] args) {
9 |
10 | Server server = new Server();
11 | server.Connect(connectString);
12 |
13 | Model model = server.Databases[0].Model;
14 |
15 | // foreach (Table table in model.Tables) {
16 | // Console.WriteLine($"Table : {table.Name}");
17 | // }
18 |
19 | Table table = model.Tables["Sales"];
20 |
21 | if (table.Measures.ContainsName("VS Code Measure")) {
22 | Measure measure = table.Measures["VS Code Measure"];
23 | measure.Expression = "\"Hello Again World\"";
24 | } else {
25 | Measure measure = new Measure() {
26 | Name = "VS Code Measure",
27 | Expression = "\"Hello World\""
28 | };
29 | table.Measures.Add(measure);
30 | }
31 |
32 | model.SaveChanges();
33 |
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/StarterFiles/Exercise01-Final-Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AnalysisServices.Tabular;
3 |
4 | class Program {
5 |
6 | const string connectString = "localhost:50000"; // update with port number on your machine
7 |
8 | static void Main(string[] args) {
9 |
10 | Server server = new Server();
11 | server.Connect(connectString);
12 |
13 | Model model = server.Databases[0].Model;
14 |
15 | // foreach(Table table in model.Tables) {
16 | // Console.WriteLine($"Table : {table.Name}");
17 | // }
18 |
19 | Table table = model.Tables["Sales"];
20 |
21 | if (table.Measures.ContainsName("VS Code Measure")) {
22 | Measure measure = table.Measures["VS Code Measure"];
23 | measure.Expression = "\"Hello Again World\"";
24 | }
25 | else {
26 | Measure measure = new Measure() {
27 | Name = "VS Code Measure",
28 | Expression = "\"Hello World\""
29 | };
30 | table.Measures.Add(measure);
31 | }
32 |
33 | model.SaveChanges();
34 |
35 | }
36 | }
--------------------------------------------------------------------------------
/Demos/Learning-TOM/Learning-TOM.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30626.31
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Learning-TOM", "Learning-TOM\Learning-TOM.csproj", "{281FE7D1-97C1-43C7-A9CF-B4121E919AFE}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {281FE7D1-97C1-43C7-A9CF-B4121E919AFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {281FE7D1-97C1-43C7-A9CF-B4121E919AFE}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {281FE7D1-97C1-43C7-A9CF-B4121E919AFE}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {281FE7D1-97C1-43C7-A9CF-B4121E919AFE}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {67A6AAFB-A3DF-42DE-90DB-89D54C7B169C}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/Solution/Exercise01/PBI-Tool/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/PBI-Tool.csproj",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | },
16 | {
17 | "label": "publish",
18 | "command": "dotnet",
19 | "type": "process",
20 | "args": [
21 | "publish",
22 | "${workspaceFolder}/PBI-Tool.csproj",
23 | "/property:GenerateFullPaths=true",
24 | "/consoleloggerparameters:NoSummary"
25 | ],
26 | "problemMatcher": "$msCompile"
27 | },
28 | {
29 | "label": "watch",
30 | "command": "dotnet",
31 | "type": "process",
32 | "args": [
33 | "watch",
34 | "run",
35 | "${workspaceFolder}/PBI-Tool.csproj",
36 | "/property:GenerateFullPaths=true",
37 | "/consoleloggerparameters:NoSummary"
38 | ],
39 | "problemMatcher": "$msCompile"
40 | }
41 | ]
42 | }
--------------------------------------------------------------------------------
/Solution/Exercise02/PBI-Tool/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/PBI-Tool.csproj",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | },
16 | {
17 | "label": "publish",
18 | "command": "dotnet",
19 | "type": "process",
20 | "args": [
21 | "publish",
22 | "${workspaceFolder}/PBI-Tool.csproj",
23 | "/property:GenerateFullPaths=true",
24 | "/consoleloggerparameters:NoSummary"
25 | ],
26 | "problemMatcher": "$msCompile"
27 | },
28 | {
29 | "label": "watch",
30 | "command": "dotnet",
31 | "type": "process",
32 | "args": [
33 | "watch",
34 | "run",
35 | "${workspaceFolder}/PBI-Tool.csproj",
36 | "/property:GenerateFullPaths=true",
37 | "/consoleloggerparameters:NoSummary"
38 | ],
39 | "problemMatcher": "$msCompile"
40 | }
41 | ]
42 | }
--------------------------------------------------------------------------------
/Solution/Exercise03/PBI-Tool/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/PBI-Tool.csproj",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | },
16 | {
17 | "label": "publish",
18 | "command": "dotnet",
19 | "type": "process",
20 | "args": [
21 | "publish",
22 | "${workspaceFolder}/PBI-Tool.csproj",
23 | "/property:GenerateFullPaths=true",
24 | "/consoleloggerparameters:NoSummary"
25 | ],
26 | "problemMatcher": "$msCompile"
27 | },
28 | {
29 | "label": "watch",
30 | "command": "dotnet",
31 | "type": "process",
32 | "args": [
33 | "watch",
34 | "run",
35 | "${workspaceFolder}/PBI-Tool.csproj",
36 | "/property:GenerateFullPaths=true",
37 | "/consoleloggerparameters:NoSummary"
38 | ],
39 | "problemMatcher": "$msCompile"
40 | }
41 | ]
42 | }
--------------------------------------------------------------------------------
/Solution/Exercise04/PBI-Tool/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/PBI-Tool.csproj",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | },
16 | {
17 | "label": "publish",
18 | "command": "dotnet",
19 | "type": "process",
20 | "args": [
21 | "publish",
22 | "${workspaceFolder}/PBI-Tool.csproj",
23 | "/property:GenerateFullPaths=true",
24 | "/consoleloggerparameters:NoSummary"
25 | ],
26 | "problemMatcher": "$msCompile"
27 | },
28 | {
29 | "label": "watch",
30 | "command": "dotnet",
31 | "type": "process",
32 | "args": [
33 | "watch",
34 | "run",
35 | "${workspaceFolder}/PBI-Tool.csproj",
36 | "/property:GenerateFullPaths=true",
37 | "/consoleloggerparameters:NoSummary"
38 | ],
39 | "problemMatcher": "$msCompile"
40 | }
41 | ]
42 | }
--------------------------------------------------------------------------------
/Demos/DaxFormatterExternalTool/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/DaxFormatterExternalTool.csproj",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | },
16 | {
17 | "label": "publish",
18 | "command": "dotnet",
19 | "type": "process",
20 | "args": [
21 | "publish",
22 | "${workspaceFolder}/DaxFormatterExternalTool.csproj",
23 | "/property:GenerateFullPaths=true",
24 | "/consoleloggerparameters:NoSummary"
25 | ],
26 | "problemMatcher": "$msCompile"
27 | },
28 | {
29 | "label": "watch",
30 | "command": "dotnet",
31 | "type": "process",
32 | "args": [
33 | "watch",
34 | "run",
35 | "${workspaceFolder}/DaxFormatterExternalTool.csproj",
36 | "/property:GenerateFullPaths=true",
37 | "/consoleloggerparameters:NoSummary"
38 | ],
39 | "problemMatcher": "$msCompile"
40 | }
41 | ]
42 | }
--------------------------------------------------------------------------------
/Demos/Learning-TOM/Learning-TOM/M/CustomersQuery.m:
--------------------------------------------------------------------------------
1 | let
2 | Source = Sql.Database("devcamp.database.windows.net", "WingtipSalesDB"),
3 | dbo_Customers = Source{[Schema="dbo",Item="Customers"]}[Data],
4 | RemovedOtherColumns = Table.SelectColumns(dbo_Customers,{"CustomerId", "FirstName", "LastName", "City", "State", "Zipcode", "Gender", "BirthDate", "FirstPurchaseDate", "LastPurchaseDate"}),
5 | MergedColumns = Table.CombineColumns(RemovedOtherColumns,{"FirstName", "LastName"},Combiner.CombineTextByDelimiter(" ", QuoteStyle.None),"Customer"),
6 | ReplacedFemaleValues = Table.ReplaceValue(MergedColumns,"F","Female",Replacer.ReplaceValue,{"Gender"}),
7 | ReplacedMaleValues = Table.ReplaceValue(ReplacedFemaleValues,"M","Male",Replacer.ReplaceValue,{"Gender"}),
8 | ChangedType = Table.TransformColumnTypes(ReplacedMaleValues,{{"FirstPurchaseDate", type date}, {"LastPurchaseDate", type date}, {"BirthDate", type date}}),
9 | AddedConditionalColumn = Table.AddColumn(ChangedType, "Customer Type", each if [FirstPurchaseDate] = [LastPurchaseDate] then "One-time Customer" else "Repeat Customer"),
10 | RemovedColumns = Table.RemoveColumns(AddedConditionalColumn,{"FirstPurchaseDate", "LastPurchaseDate"}),
11 | ChangedType1 = Table.TransformColumnTypes(RemovedColumns,{{"Customer Type", type text}}),
12 | RenamedColumns = Table.RenameColumns(ChangedType1,{{"City", "City Name"}}),
13 | AddedCustom = Table.AddColumn(RenamedColumns, "City", each [City Name] & ", " + [State]),
14 | ChangedType2 = Table.TransformColumnTypes(AddedCustom,{{"City", type text}})
15 | in
16 | ChangedType2
--------------------------------------------------------------------------------
/Demos/DAXFormatter.pbitool.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0",
3 | "name": "DAX Formatter",
4 | "description": "Auto Format DAX",
5 | "path": "C:\\DevCamp\\Tom-Tutorial\\PBI-Tool\\bin\\Debug\\net5.0\\PBI-Tool.exe",
6 | "arguments": "%server%",
7 | "iconData": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPsAAAD6CAYAAABnLjEDAAAACXBIWXMAAAsSAAALEgHS3X78AAADp0lEQVR4nO3WsXUaQRRA0VkfCnAF1OCQzKgCWnMrVICUEaoGKqCD9RHSsWNLsr3LuzdR+ufPPjTTPM8DuH9f3DE0iB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0iNi/HvBx2exfOn9gez48fWVjkm7tuj+fnBcxxs3n7e/rPc7A+0wcnLnxzT2OMxfyoecZDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghYvN2zAcXzj9W+OauC5jhl2me54WMAvxNnvEQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iNi46M9zOey+jTG+3st5eLU9nh/fs4rLYbdfygpfziD2z/VjjPH9ng7EzfTONZwWtL7JMx4ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4R0zzP7hoC/GeHCLFDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ8Rmjce8HHb7BYyxBtft8fxcXwKvVhn7GOO0gBnW4GmM4YeRG894iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RGxWesyHBcywBtf6AvhtmufZOiDAMx4ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4FY4yf5wcsaJ/89DEAAAAASUVORK5CYII="
8 | }
9 |
10 |
--------------------------------------------------------------------------------
/Solution/Exercise04/DAXFormatter.pbitool.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0",
3 | "name": "DAX Formatter",
4 | "description": "Auto Format DAX",
5 | "path": "C:\\DevCamp\\Tom-Tutorial\\PBI-Tool\\bin\\Debug\\net5.0\\PBI-Tool.exe",
6 | "arguments": "%server%",
7 | "iconData": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPsAAAD6CAYAAABnLjEDAAAACXBIWXMAAAsSAAALEgHS3X78AAADp0lEQVR4nO3WsXUaQRRA0VkfCnAF1OCQzKgCWnMrVICUEaoGKqCD9RHSsWNLsr3LuzdR+ufPPjTTPM8DuH9f3DE0iB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0iNi/HvBx2exfOn9gez48fWVjkm7tuj+fnBcxxs3n7e/rPc7A+0wcnLnxzT2OMxfyoecZDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghYvN2zAcXzj9W+OauC5jhl2me54WMAvxNnvEQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iNi46M9zOey+jTG+3st5eLU9nh/fs4rLYbdfygpfziD2z/VjjPH9ng7EzfTONZwWtL7JMx4ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4R0zzP7hoC/GeHCLFDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ8Rmjce8HHb7BYyxBtft8fxcXwKvVhn7GOO0gBnW4GmM4YeRG894iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RGxWesyHBcywBtf6AvhtmufZOiDAMx4ixA4RYocIsUOE2CFC7BAhdogQO0SIHSLEDhFihwixQ4TYIULsECF2iBA7RIgdIsQOEWKHCLFDhNghQuwQIXaIEDtEiB0ixA4FY4yf5wcsaJ/89DEAAAAASUVORK5CYII="
8 | }
9 |
10 |
--------------------------------------------------------------------------------
/Solution/Exercise02/PBI-Tool/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AnalysisServices.Tabular;
3 |
4 | class Program {
5 |
6 | const string connectString = "localhost:57308"; // update for port number on your machine
7 |
8 | static void Main(string[] args) {
9 |
10 | Server server = new Server();
11 | server.Connect(connectString);
12 | Model model = server.Databases[0].Model;
13 |
14 | foreach (Table table in model.Tables) {
15 | foreach (Column column in table.Columns) {
16 | // determine if column is visible and numeric
17 | if ((column.IsHidden == false) &
18 | (column.DataType == DataType.Int64 ||
19 | column.DataType == DataType.Decimal ||
20 | column.DataType == DataType.Double)) {
21 |
22 | // add automeasure for this column new measure
23 | string measureName = $"Sum of {column.Name} ({table.Name})";
24 | string expression = $"SUM('{table.Name}'[{column.Name}])";
25 | string displayFolder = "Auto Measures";
26 |
27 | Measure measure = new Measure() {
28 | Name = measureName,
29 | Expression = expression,
30 | DisplayFolder = displayFolder,
31 | Description = displayFolder
32 | };
33 |
34 | measure.Annotations.Add(new Annotation() { Value = "This is an Auto Measure" });
35 |
36 | if (!table.Measures.ContainsName(measureName)) {
37 | table.Measures.Add(measure);
38 | } else {
39 | table.Measures[measureName].Expression = expression;
40 | table.Measures[measureName].DisplayFolder = displayFolder;
41 | }
42 | }
43 | }
44 | }
45 | // save changes back to model in Power BI Desktop
46 | model.SaveChanges();
47 | }
48 |
49 | }
--------------------------------------------------------------------------------
/StarterFiles/Exercise02-Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.AnalysisServices.Tabular;
3 |
4 | class Program {
5 |
6 | const string connectString = "localhost:50000"; // update for port number on your machine
7 |
8 | static void Main(string[] args) {
9 |
10 | Server server = new Server();
11 | server.Connect(connectString);
12 | Model model = server.Databases[0].Model;
13 |
14 | foreach (Table table in model.Tables) {
15 | foreach (Column column in table.Columns) {
16 | // determine if column is visible and numeric
17 | if ((column.IsHidden == false) &
18 | (column.DataType == DataType.Int64 ||
19 | column.DataType == DataType.Decimal ||
20 | column.DataType == DataType.Double)) {
21 |
22 | // add automeasure for this column new measure
23 | string measureName = $"Sum of {column.Name} ({table.Name})";
24 | string expression = $"SUM('{table.Name}'[{column.Name}])";
25 | string displayFolder = "Auto Measures";
26 |
27 | Measure measure = new Measure() {
28 | Name = measureName,
29 | Expression = expression,
30 | DisplayFolder = displayFolder,
31 | Description = displayFolder
32 | };
33 |
34 | measure.Annotations.Add(new Annotation() { Value = "This is an Auto Measure" });
35 |
36 | if (!table.Measures.ContainsName(measureName)) {
37 | table.Measures.Add(measure);
38 | }
39 | else {
40 | table.Measures[measureName].Expression = expression;
41 | table.Measures[measureName].DisplayFolder = displayFolder;
42 | }
43 | }
44 | }
45 | }
46 | // save changes back to model in Power BI Desktop
47 | model.SaveChanges();
48 | }
49 |
50 | }
--------------------------------------------------------------------------------
/Solution/Exercise03/PBI-Tool/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using TOM = Microsoft.AnalysisServices.Tabular;
3 | using Microsoft.AnalysisServices.AdomdClient;
4 | using System.Diagnostics;
5 | using System.IO;
6 |
7 | class Program {
8 |
9 | const string connectString = "Data Source=localhost:57308"; // Update port number on your machine
10 |
11 | static void Main(string[] args) {
12 |
13 | // Exercise 3 - Part 1
14 | // ExecuteDaxQuery();
15 |
16 | // Exercise 3 - Part 2
17 | AddSalesRegionMeasures();
18 | }
19 |
20 | static void ExecuteDaxQuery() {
21 |
22 | // DAX query to be submitted totabuar database engine
23 | String query = @"
24 | EVALUATE
25 | SUMMARIZECOLUMNS(
26 | //GROUP BY
27 | Customers[State],
28 |
29 | //FILTER BY
30 | TREATAS( {""Western Region""} , 'Customers'[Sales Region] ) ,
31 |
32 | // MEASURES
33 | ""Sales Revenue"" , SUM(Sales[SalesAmount]) ,
34 | ""Units Sold"" , SUM(Sales[Quantity])
35 | )
36 | ";
37 |
38 | AdomdConnection adomdConnection = new AdomdConnection(connectString);
39 | adomdConnection.Open();
40 |
41 | AdomdCommand adomdCommand = new AdomdCommand(query, adomdConnection);
42 | AdomdDataReader reader = adomdCommand.ExecuteReader();
43 |
44 | ConvertReaderToCsv(reader);
45 |
46 | reader.Dispose();
47 | adomdConnection.Close();
48 |
49 | }
50 |
51 | static void ConvertReaderToCsv(AdomdDataReader reader, bool openinExcel = true) {
52 |
53 | string csv = string.Empty;
54 |
55 | for (int col = 0; col < reader.FieldCount; col++) {
56 | csv += reader.GetName(col);
57 | csv += (col < (reader.FieldCount - 1)) ? "," : "\n";
58 | }
59 |
60 | // Create a loop for every row in the resultset
61 | while (reader.Read()) {
62 | // Create a loop for every column in the current row
63 | for (int i = 0; i < reader.FieldCount; i++) {
64 | csv += reader.GetValue(i);
65 | csv += (i < (reader.FieldCount - 1)) ? "," : "\n";
66 | }
67 | }
68 |
69 | string filePath = System.IO.Directory.GetCurrentDirectory() + @"\QueryResuts.csv";
70 | StreamWriter writer = File.CreateText(filePath);
71 | writer.Write(csv);
72 | writer.Flush();
73 | writer.Dispose();
74 |
75 | if (openinExcel) {
76 | OpenInExcel(filePath);
77 | }
78 |
79 | }
80 |
81 | static void OpenInExcel(string FilePath) {
82 |
83 | ProcessStartInfo startInfo = new ProcessStartInfo();
84 |
85 | bool excelFound = false;
86 | if (File.Exists("C:\\Program Files\\Microsoft Office\\root\\Office16\\EXCEL.EXE")) {
87 | startInfo.FileName = "C:\\Program Files\\Microsoft Office\\root\\Office16\\EXCEL.EXE";
88 | excelFound = true;
89 | } else {
90 | if (File.Exists("C:\\Program Files (x86)\\Microsoft Office\\root\\Office16\\EXCEL.EXE")) {
91 | startInfo.FileName = "C:\\Program Files (x86)\\Microsoft Office\\root\\Office16\\EXCEL.EXE";
92 | excelFound = true;
93 | }
94 | }
95 | if (excelFound) {
96 | startInfo.Arguments = FilePath;
97 | Process.Start(startInfo);
98 | } else {
99 | System.Console.WriteLine("Coud not find Microsoft Exce on this PC.");
100 | }
101 |
102 | }
103 |
104 | static void AddSalesRegionMeasures() {
105 |
106 | // DAX query to be submitted totabuar database engine
107 | String query = "EVALUATE( VALUES(Customers[Sales Region]) )";
108 |
109 | AdomdConnection adomdConnection = new AdomdConnection(connectString);
110 | adomdConnection.Open();
111 |
112 | AdomdCommand adomdCommand = new AdomdCommand(query, adomdConnection);
113 | AdomdDataReader reader = adomdCommand.ExecuteReader();
114 |
115 | // open connection use TOM to create new measures
116 | TOM.Server server = new TOM.Server();
117 | server.Connect(connectString);
118 | TOM.Model model = server.Databases[0].Model;
119 | TOM.Table salesTable = model.Tables["Sales"];
120 |
121 | String measureDescription = "Auto Measures";
122 | // delete any previously created "Auto" measures
123 | foreach (TOM.Measure m in salesTable.Measures) {
124 | if (m.Description == measureDescription) {
125 | salesTable.Measures.Remove(m);
126 | model.SaveChanges();
127 | }
128 | }
129 |
130 | // Create the new measures
131 | while (reader.Read()) {
132 | String SalesRegion = reader.GetValue(0).ToString();
133 | String measureName = $"{SalesRegion} Sales";
134 |
135 | TOM.Measure measure = new TOM.Measure() {
136 | Name = measureName,
137 | Description = measureDescription,
138 | DisplayFolder = "Auto Measures",
139 | FormatString = "$#,##0",
140 | Expression = $@"CALCULATE( SUM(Sales[SalesAmount]), Customers[Sales Region] = ""{SalesRegion}"" )"
141 | };
142 |
143 | salesTable.Measures.Add(measure);
144 | }
145 |
146 | model.SaveChanges();
147 | reader.Dispose();
148 | adomdConnection.Close();
149 |
150 | }
151 | }
--------------------------------------------------------------------------------
/StarterFiles/Exercise03-Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using TOM = Microsoft.AnalysisServices.Tabular;
3 | using Microsoft.AnalysisServices.AdomdClient;
4 | using System.Diagnostics;
5 | using System.IO;
6 |
7 | class Program {
8 |
9 | const string connectString = "Data Source=localhost:50000"; // Update port number on your machine
10 |
11 | static void Main(string[] args) {
12 |
13 | // Exercise 3 - Part 1
14 | ExecuteDaxQuery();
15 |
16 | // Exercise 3 - Part 2
17 | // AddSalesRegionMeasures();
18 | }
19 |
20 | static void ExecuteDaxQuery() {
21 |
22 | // DAX query to be submitted totabuar database engine
23 | String query = @"
24 | EVALUATE
25 | SUMMARIZECOLUMNS(
26 | //GROUP BY
27 | Customers[State],
28 |
29 | //FILTER BY
30 | TREATAS( {""Western Region""} , 'Customers'[Sales Region] ) ,
31 |
32 | // MEASURES
33 | ""Sales Revenue"" , SUM(Sales[SalesAmount]) ,
34 | ""Units Sold"" , SUM(Sales[Quantity])
35 | )
36 | ";
37 |
38 | AdomdConnection adomdConnection = new AdomdConnection(connectString);
39 | adomdConnection.Open();
40 |
41 | AdomdCommand adomdCommand = new AdomdCommand(query, adomdConnection);
42 | AdomdDataReader reader = adomdCommand.ExecuteReader();
43 |
44 | ConvertReaderToCsv(reader);
45 |
46 | reader.Dispose();
47 | adomdConnection.Close();
48 |
49 | }
50 |
51 | static void ConvertReaderToCsv(AdomdDataReader reader, bool openinExcel = true) {
52 |
53 | string csv = string.Empty;
54 |
55 | for (int col = 0; col < reader.FieldCount; col++) {
56 | csv += reader.GetName(col);
57 | csv += (col < (reader.FieldCount - 1)) ? "," : "\n";
58 | }
59 |
60 | // Create a loop for every row in the resultset
61 | while (reader.Read()) {
62 | // Create a loop for every column in the current row
63 | for (int i = 0; i < reader.FieldCount; i++) {
64 | csv += reader.GetValue(i);
65 | csv += (i < (reader.FieldCount - 1)) ? "," : "\n";
66 | }
67 | }
68 |
69 | string filePath = System.IO.Directory.GetCurrentDirectory() + @"\QueryResuts.csv";
70 | StreamWriter writer = File.CreateText(filePath);
71 | writer.Write(csv);
72 | writer.Flush();
73 | writer.Dispose();
74 |
75 | if (openinExcel) {
76 | OpenInExcel(filePath);
77 | }
78 |
79 | }
80 |
81 | static void OpenInExcel(string FilePath) {
82 |
83 | ProcessStartInfo startInfo = new ProcessStartInfo();
84 |
85 | bool excelFound = false;
86 | if (File.Exists("C:\\Program Files\\Microsoft Office\\root\\Office16\\EXCEL.EXE")) {
87 | startInfo.FileName = "C:\\Program Files\\Microsoft Office\\root\\Office16\\EXCEL.EXE";
88 | excelFound = true;
89 | }
90 | else {
91 | if (File.Exists("C:\\Program Files (x86)\\Microsoft Office\\root\\Office16\\EXCEL.EXE")) {
92 | startInfo.FileName = "C:\\Program Files (x86)\\Microsoft Office\\root\\Office16\\EXCEL.EXE";
93 | excelFound = true;
94 | }
95 | }
96 | if (excelFound) {
97 | startInfo.Arguments = FilePath;
98 | Process.Start(startInfo);
99 | }
100 | else {
101 | System.Console.WriteLine("Coud not find Microsoft Exce on this PC.");
102 | }
103 |
104 | }
105 |
106 | static void AddSalesRegionMeasures() {
107 |
108 | // DAX query to be submitted totabuar database engine
109 | String query = "EVALUATE( VALUES(Customers[Sales Region]) )";
110 |
111 | AdomdConnection adomdConnection = new AdomdConnection(connectString);
112 | adomdConnection.Open();
113 |
114 | AdomdCommand adomdCommand = new AdomdCommand(query, adomdConnection);
115 | AdomdDataReader reader = adomdCommand.ExecuteReader();
116 |
117 | // open connection use TOM to create new measures
118 | TOM.Server server = new TOM.Server();
119 | server.Connect(connectString);
120 | TOM.Model model = server.Databases[0].Model;
121 | TOM.Table salesTable = model.Tables["Sales"];
122 |
123 | String measureDescription = "Auto Measures";
124 | // delete any previously created "Auto" measures
125 | foreach (TOM.Measure m in salesTable.Measures) {
126 | if (m.Description == measureDescription) {
127 | salesTable.Measures.Remove(m);
128 | model.SaveChanges();
129 | }
130 | }
131 |
132 | // Create the new measures
133 | while (reader.Read()) {
134 | String SalesRegion = reader.GetValue(0).ToString();
135 | String measureName = $"{SalesRegion} Sales";
136 |
137 | TOM.Measure measure = new TOM.Measure() {
138 | Name = measureName,
139 | Description = measureDescription,
140 | DisplayFolder = "Auto Measures",
141 | FormatString = "$#,##0",
142 | Expression = $@"CALCULATE( SUM(Sales[SalesAmount]), Customers[Sales Region] = ""{SalesRegion}"" )"
143 | };
144 |
145 | salesTable.Measures.Add(measure);
146 | }
147 |
148 | model.SaveChanges();
149 | reader.Dispose();
150 | adomdConnection.Close();
151 |
152 | }
153 | }
--------------------------------------------------------------------------------
/Demos/Learning-TOM/Learning-TOM/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | ..\dax\calculatedcolumn-agegroup.dax;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
123 |
124 |
125 | ..\dax\calculatedcolumn-salesregionsort.dax;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
126 |
127 |
128 | ..\dax\calculatedcolumn-salesregion.dax;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
129 |
130 |
131 | ..\dax\calculatedtable-calendar.dax;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
132 |
133 |
134 | ..\m\customersquery.m;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
135 |
136 |
137 | ..\m\productquery.m;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
138 |
139 |
140 | ..\dax\querygetsalesbystate.dax;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
141 |
142 |
143 | ..\m\salesquery.m;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
144 |
145 |
--------------------------------------------------------------------------------
/Demos/Learning-TOM/Learning-TOM/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace Learning_TOM.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Learning_TOM.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized string similar to SWITCH (
65 | /// TRUE(),
66 | /// [Age] >= 65, "65 and over",
67 | /// [Age] >= 50, "50 to 64",
68 | /// [Age] >= 40, "40 to 49",
69 | /// [Age] >= 30, "30 to 39",
70 | /// [Age] >= 18, "18 to 29",
71 | /// [Age] < 18, "Under 18"
72 | ///).
73 | ///
74 | internal static string CalculatedColumn_AgeGroup_dax {
75 | get {
76 | return ResourceManager.GetString("CalculatedColumn_AgeGroup_dax", resourceCulture);
77 | }
78 | }
79 |
80 | ///
81 | /// Looks up a localized string similar to SWITCH(
82 | /// TRUE(),
83 | /// [State] in {"AK", "AZ", "CA", "CO", "HI", "MT", "NM", "NV", "OR", "UT", "WA"},
84 | /// "Western Region",
85 | /// [State] in {"AL", "AR", "IA", "ID", "IL", "IN", "KS", "KY", "LA", "MI", "MN", "MO", "MS", "ND", "NE", "OH", "OK", "SD", "TN", "TX", "WI", "WV", "WY"},
86 | /// "Central Region",
87 | /// [State] in {"CT", "DE", "FL", "GA", "MA", "MD", "ME", "NC", "NH", "NJ", "NY", "PA", "RI", "SC", "VA", "VT"},
88 | /// "Eastern Region"
89 | ///).
90 | ///
91 | internal static string CalculatedColumn_SalesRegion_dax {
92 | get {
93 | return ResourceManager.GetString("CalculatedColumn_SalesRegion_dax", resourceCulture);
94 | }
95 | }
96 |
97 | ///
98 | /// Looks up a localized string similar to SWITCH(
99 | /// TRUE(),
100 | /// [State] in {"AK", "AZ", "CA", "CO", "HI", "MT", "NM", "NV", "OR", "UT", "WA"},
101 | /// 1,
102 | /// [State] in {"AL", "AR", "IA", "ID", "IL", "IN", "KS", "KY", "LA", "MI", "MN", "MO", "MS", "ND", "NE", "OH", "OK", "SD", "TN", "TX", "WI", "WV", "WY"},
103 | /// 2,
104 | /// [State] in {"CT", "DE", "FL", "GA", "MA", "MD", "ME", "NC", "NH", "NJ", "NY", "PA", "RI", "SC", "VA", "VT"},
105 | /// 3
106 | ///).
107 | ///
108 | internal static string CalculatedColumn_SalesRegionSort_m {
109 | get {
110 | return ResourceManager.GetString("CalculatedColumn_SalesRegionSort_m", resourceCulture);
111 | }
112 | }
113 |
114 | ///
115 | /// Looks up a localized string similar to Var CalenderStart = Date(Year(Min(Sales[InvoiceDate])) , 1, 1)
116 | ///Var CalendarEnd = Date(Year(MAX(Sales[InvoiceDate])), 12, 31)
117 | ///Return
118 | /// CALENDAR(CalenderStart, CalendarEnd)
119 | ///.
120 | ///
121 | internal static string CalculatedTable_Calendar_dax {
122 | get {
123 | return ResourceManager.GetString("CalculatedTable_Calendar_dax", resourceCulture);
124 | }
125 | }
126 |
127 | ///
128 | /// Looks up a localized string similar to let
129 | /// Source = Sql.Database("devcamp.database.windows.net", "WingtipSalesDB"),
130 | /// dbo_Customers = Source{[Schema="dbo",Item="Customers"]}[Data],
131 | /// RemovedOtherColumns = Table.SelectColumns(dbo_Customers,{"CustomerId", "FirstName", "LastName", "City", "State", "Zipcode", "Gender", "BirthDate", "FirstPurchaseDate", "LastPurchaseDate"}),
132 | /// MergedColumns = Table.CombineColumns(RemovedOtherColumns,{"FirstName", "LastName"},Combiner.CombineTextByDelimiter(" ", QuoteStyle.None),"Customer"),
133 | /// Replace [rest of string was truncated]";.
134 | ///
135 | internal static string CustomersQuery_m {
136 | get {
137 | return ResourceManager.GetString("CustomersQuery_m", resourceCulture);
138 | }
139 | }
140 |
141 | ///
142 | /// Looks up a localized string similar to let
143 | /// Source = Sql.Database("devcamp.database.windows.net", "WingtipSalesDB"),
144 | /// dbo_Products = Source{[Schema="dbo",Item="Products"]}[Data],
145 | /// RemovedOtherColumns = Table.SelectColumns(dbo_Products,{"ProductId", "Title", "Description", "ProductCategory", "ProductImageUrl"}),
146 | /// RenamedColumns = Table.RenameColumns(RemovedOtherColumns,{{"Title", "Product"}}),
147 | /// SplitColumnByDelimiter = Table.SplitColumn(RenamedColumns, "ProductCategory", Splitter.SplitTextByDelimiter(" > ", QuoteStyle.Csv), {" [rest of string was truncated]";.
148 | ///
149 | internal static string ProductQuery_m {
150 | get {
151 | return ResourceManager.GetString("ProductQuery_m", resourceCulture);
152 | }
153 | }
154 |
155 | ///
156 | /// Looks up a localized string similar to EVALUATE
157 | /// SUMMARIZECOLUMNS(
158 | /// Customers[State],
159 | /// "Sales Revenue", SUM(Sales[SalesAmount]),
160 | /// "Units Sold", SUM(Sales[Quantity])
161 | ///).
162 | ///
163 | internal static string QueryGetSalesByState_dax {
164 | get {
165 | return ResourceManager.GetString("QueryGetSalesByState_dax", resourceCulture);
166 | }
167 | }
168 |
169 | ///
170 | /// Looks up a localized string similar to let
171 | /// Source = Sql.Database("devcamp.database.windows.net", "WingtipSalesDB"),
172 | /// dbo_InvoiceDetails = Source{[Schema="dbo",Item="InvoiceDetails"]}[Data],
173 | /// ExpandedInvoices = Table.ExpandRecordColumn(dbo_InvoiceDetails, "Invoices", {"InvoiceDate", "CustomerId"}, {"InvoiceDate", "CustomerId"}),
174 | /// RemovedColumns = Table.RemoveColumns(ExpandedInvoices,{"Products"}),
175 | /// ChangedType = Table.TransformColumnTypes(RemovedColumns,{{"InvoiceDate", type date}, {"SalesAmount", Currency.Type}})
176 | ///in
177 | /// Ch [rest of string was truncated]";.
178 | ///
179 | internal static string SalesQuery_m {
180 | get {
181 | return ResourceManager.GetString("SalesQuery_m", resourceCulture);
182 | }
183 | }
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/Demos/DaxFormatterExternalTool/DaxFormatter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Net;
4 | using System.Net.Http;
5 | using System.Net.Http.Headers;
6 | using System.Text;
7 | using Newtonsoft.Json;
8 | using Microsoft.AnalysisServices.Tabular;
9 | using System.Security.Cryptography;
10 |
11 | namespace DaxFormatterExternalTool
12 | {
13 |
14 | class DaxFormatter
15 | {
16 |
17 | private const string restUrl = "https://daxformatter.azurewebsites.net/api/daxformatter/DaxTextFormat";
18 |
19 | public static void FormatDaxForModel(Model model)
20 | {
21 |
22 | Console.WriteLine("Iterating measure, calculated columns and calulated tables for DAX formatting...");
23 | Console.WriteLine();
24 |
25 | foreach (Table table in model.Tables)
26 | {
27 |
28 | // check which measures require formatting
29 | foreach (var measure in table.Measures)
30 | {
31 | Console.Write("Measure: " + table.Name + "[" + measure.Name + "]");
32 | if (MeasureRequiresFormatting(measure))
33 | {
34 | Console.WriteLine(" - formatting DAX...");
35 | string expressionOwner = measure.Name;
36 | string originalDaxExpression = measure.Expression;
37 | string formattedDaxExpression = DaxFormatter.FormatDaxExpression(originalDaxExpression, expressionOwner);
38 | // write formatted expression back to measure
39 | measure.Expression = formattedDaxExpression;
40 | // store hash of formatted expression for later comparison
41 | string hashedDaxExpression = GetHashValueAsString(formattedDaxExpression);
42 | if (measure.Annotations.Contains("HashedExpression"))
43 | {
44 | measure.Annotations["HashedExpression"].Value = hashedDaxExpression;
45 | }
46 | else
47 | {
48 | measure.Annotations.Add(new Annotation { Name = "HashedExpression", Value = hashedDaxExpression });
49 | }
50 | }
51 | else
52 | {
53 | Console.WriteLine(" - DAX already formatted");
54 | }
55 | }
56 |
57 | // check which calculated columns require formatting
58 | foreach (var column in table.Columns)
59 | {
60 | if (column.Type == ColumnType.Calculated)
61 | {
62 | CalculatedColumn col = (CalculatedColumn)column;
63 | Console.Write("Calculated column: " + table.Name + "[" + col.Name + "]");
64 | if (CalculatedColumnRequiresFormatting(col))
65 | {
66 | Console.WriteLine(" - formatting DAX...");
67 | string expressionOwner = "'" + table.Name + "'[" + col.Name + "]";
68 | string originalDaxExpression = col.Expression;
69 | string formattedDaxExpression = DaxFormatter.FormatDaxExpression(originalDaxExpression, expressionOwner);
70 | // write formatted expression back to calculated column
71 | col.Expression = formattedDaxExpression;
72 | // store hash of formatted expression for later comparison
73 | string hashedDaxExpression = GetHashValueAsString(formattedDaxExpression);
74 | if (col.Annotations.Contains("HashedExpression"))
75 | {
76 | col.Annotations["HashedExpression"].Value = hashedDaxExpression;
77 | }
78 | else
79 | {
80 | col.Annotations.Add(new Annotation { Name = "HashedExpression", Value = hashedDaxExpression });
81 | }
82 | }
83 | else
84 | {
85 | Console.WriteLine(" - DAX already formatted");
86 | }
87 | }
88 |
89 | }
90 |
91 | // check which calculated tables require formatting
92 | if ((table.Partitions.Count > 0) &&
93 | (table.Partitions[0].SourceType == PartitionSourceType.Calculated))
94 | {
95 | Console.Write("Calculated table: " + table.Name);
96 | if (CalculatedTableRequiresFormatting(table))
97 | {
98 | var source = table.Partitions[0].Source as CalculatedPartitionSource;
99 | Console.WriteLine(" - formatting DAX...");
100 | string expressionOwner = table.Name;
101 | string originalDaxExpression = source.Expression;
102 | string formattedDaxExpression = DaxFormatter.FormatDaxExpression(originalDaxExpression, expressionOwner);
103 | // write formatted expression back to calculated column
104 | source.Expression = formattedDaxExpression;
105 | // store hash of formatted expression for later comparison
106 | string hashedDaxExpression = GetHashValueAsString(formattedDaxExpression);
107 | if (table.Annotations.Contains("HashedExpression"))
108 | {
109 | table.Annotations["HashedExpression"].Value = hashedDaxExpression;
110 | }
111 | else
112 | {
113 | table.Annotations.Add(new Annotation { Name = "HashedExpression", Value = hashedDaxExpression });
114 | }
115 |
116 | }
117 | else
118 | {
119 | Console.WriteLine(" - DAX already formatted ");
120 | }
121 | }
122 |
123 | }
124 |
125 | model.RequestRefresh(RefreshType.Automatic);
126 | model.SaveChanges();
127 |
128 | Console.WriteLine();
129 | Console.WriteLine("Press any key to continue");
130 | Console.ReadLine();
131 | }
132 |
133 | private static string FormatDaxExpression(string daxInput, string expressionOwner = "")
134 | {
135 |
136 | string prefix = string.IsNullOrEmpty(expressionOwner) ? "" : expressionOwner + " =";
137 |
138 | string daxExpression = prefix + daxInput;
139 |
140 | RequestBody requestBody = new RequestBody
141 | {
142 | CallerApp = "Apollo",
143 | Dax = daxExpression,
144 | DecimalSeparator = ".",
145 | ListSeparator = ",",
146 | DatabaseCompatibilityLevel = "520",
147 | SkipSpaceAfterFunctionName = false
148 | };
149 |
150 | string postBody = JsonConvert.SerializeObject(requestBody);
151 |
152 | HttpContent body = new StringContent(postBody);
153 | body.Headers.ContentType = new MediaTypeWithQualityHeaderValue("application/json");
154 | HttpClient client = new HttpClient();
155 | client.DefaultRequestHeaders.Add("Accept", "application/json; charset=UTF-8");
156 | HttpResponseMessage response = client.PostAsync(restUrl, body).Result;
157 |
158 | if (response.IsSuccessStatusCode)
159 | {
160 | string jsonResponse = response.Content.ReadAsStringAsync().Result;
161 | ResponseBody responseBody = JsonConvert.DeserializeObject(jsonResponse);
162 | string formattedExression = responseBody.formatted.Replace(prefix, "").Replace("\r\n", "\n");
163 | return formattedExression;
164 | }
165 | else
166 | {
167 | Console.WriteLine();
168 | Console.WriteLine("OUCH! - error occurred during POST REST call");
169 | Console.WriteLine();
170 | return string.Empty;
171 | }
172 | }
173 |
174 | private static bool CalculatedColumnRequiresFormatting(CalculatedColumn column)
175 | {
176 | if (!column.Annotations.Contains("HashedExpression"))
177 | {
178 | return true;
179 | }
180 | string daxExpression = column.Expression;
181 | string hashedDaxExpression = GetHashValueAsString(daxExpression);
182 | string lastStoredHash = column.Annotations["HashedExpression"].Value;
183 | return hashedDaxExpression != lastStoredHash;
184 | }
185 |
186 | private static bool MeasureRequiresFormatting(Measure measure)
187 | {
188 | if (!measure.Annotations.Contains("HashedExpression"))
189 | {
190 | return true;
191 | }
192 | string daxExpression = measure.Expression;
193 | string hashedDaxExpression = GetHashValueAsString(daxExpression);
194 | string lastStoredHash = measure.Annotations["HashedExpression"].Value;
195 | return hashedDaxExpression != lastStoredHash;
196 | }
197 |
198 | private static bool CalculatedTableRequiresFormatting(Table table)
199 | {
200 | if (!table.Annotations.Contains("HashedExpression"))
201 | {
202 | return true;
203 | }
204 | var source = table.Partitions[0].Source as CalculatedPartitionSource;
205 | string daxExpression = source.Expression;
206 | string hashedDaxExpression = GetHashValueAsString(daxExpression);
207 | string lastStoredHash = table.Annotations["HashedExpression"].Value;
208 | return hashedDaxExpression != lastStoredHash;
209 | }
210 |
211 | private static string GetHashValueAsString(string input)
212 | {
213 | byte[] data = Encoding.UTF8.GetBytes(input);
214 | using (var shaM = new SHA1Managed())
215 | {
216 | byte[] result = shaM.ComputeHash(data);
217 | return Convert.ToBase64String(result);
218 | }
219 | }
220 |
221 | }
222 |
223 | class RequestBody
224 | {
225 | public string Dax { get; set; }
226 | public object MaxLineLenght { get; set; }
227 | public bool SkipSpaceAfterFunctionName { get; set; }
228 | public string ListSeparator { get; set; }
229 | public string DecimalSeparator { get; set; }
230 | public string CallerApp { get; set; }
231 | public string CallerVersion { get; set; }
232 | public string ServerName { get; set; }
233 | public string ServerEdition { get; set; }
234 | public string ServerType { get; set; }
235 | public string ServerMode { get; set; }
236 | public string ServerLocation { get; set; }
237 | public string ServerVersion { get; set; }
238 | public string DatabaseName { get; set; }
239 | public string DatabaseCompatibilityLevel { get; set; }
240 | }
241 |
242 | class ResponseBody
243 | {
244 | public string formatted { get; set; }
245 | public List