使用表格对象模型 (TOM) 对 Power BI 语义模型进行编程

适用于:SQL Server 2016 及更高版本的 Analysis Services Azure Analysis Services Fabric/Power BI Premium

本文最初由 Power BI 客户咨询团队 (适用于 Power BI Dev Camp 的 CAT) 创建,这是有关 Power BI 高级编程的会话、文章和视频的集合。

Power BI Premium语义模型包括 XMLA 终结点。 终结点对 Power BI 开发人员非常重要,因为它提供 API 来与 Power BI 服务中运行的 Analysis Services 引擎交互,并直接针对 Power BI 模型进行编程。 越来越多的 Power BI 专业人员发现,他们可以使用预先存在的使用 XMLA 协议的工具(如 SQL Server Management Studio、表格编辑器和 DAX Studio)来创建、查看和管理 Power BI 模型。 作为 .NET 开发人员,现在可以在 .NET 应用程序中编写 C# 代码,以直接在 Power BI 服务中创建和修改模型。

表格对象模型 (TOM) 是一个 .NET 库,在 XMLA 终结点的顶部提供抽象层。 它允许开发人员根据直观的编程模型编写代码,该模型包括 模型度量值等类。 在后台,TOM 将代码中的读取和写入操作转换为针对 XMLA 终结点执行的 HTTP 请求。

要通过 XMLA 终结点建模的应用程序示意图。

本文重点介绍 TOM 入门,并演示如何编写在 Power BI 服务中运行时创建和修改模型所需的 C# 代码。 但是,TOM 也可用于不涉及 XMLA 终结点的方案,例如针对在 Power BI Desktop 中运行的本地模型进行编程时。 若要详细了解如何将 TOM 与 Power BI Desktop 配合使用,请参阅 Power BI CAT 成员 Phil Seamark 的博客系列,并确保watch Power BI 开发营中如何使用表格对象模型对数据集进行编程 (TOM) 视频。

TOM 为 Power BI 开发人员提供了一个独立于 Power BI REST API 且与众不同的全新强大 API。 虽然这两个 API 之间存在一些重叠,但其中每个 API 都包含大量未包含在另一个 API 中的功能。 此外,在某些情况下,开发人员需要同时使用这两个 API 来实现完整的解决方案。

使用表格对象模型入门

在使用 TOM 进行编程之前,首先需要获取工作区连接的 URL。 工作区连接 URL 引用特定的工作区,并用于创建一个连接字符串,使代码能够连接到该 Power BI 工作区和在其中运行的模型。 首先导航到在专用容量中运行的 Power BI 工作区的 “设置” 页。

工作区设置的链接。

注意

仅在专用容量中运行的模型支持 XMLA 终结点。 它不适用于以共享容量运行的模型。 如果使用每个用户容量Power BI Premium中的模型,则可以以用户身份进行连接,但不能作为服务主体进行连接。

导航到“设置”窗格的“高级”选项卡后,将“工作区连接 URL”复制到剪贴板。

语义模型设置中的工作区连接字符串。

下一步是创建新的 .NET 应用程序,在其中编写使用 TOM 进行程序的 C# 代码。 可以在.NET Framework上使用 .NET 5、.NET Core 3.1 或更早版本创建 Web 应用程序或桌面应用程序。 在本文中,我们将使用 .NET 5 SDK 创建一个简单的 C# 控制台应用程序。

创建新的控制台应用程序

首先使用 .NET CLI 创建新的控制台应用程序。

dotnet new console --name`

添加表格对象模型 NuGet 包

创建控制台应用程序后,添加 Microsoft.AnalysisServices.AdomdClient.NetCore.retail.amd64 NuGet 包,其中包含表格对象模型 (TOM) 。 可以使用以下 .NET CLI 在 .NET 5 应用程序中安装包:

dotnet add package Microsoft.AnalysisServices.NetCore.retail.amd64

添加连接字符串

如果项目安装了 NuGet 包并安装了 TOM 库,则可以使用 TOM 创建传统的Hello World应用程序。 应用程序使用工作区连接 URL 连接到 Power BI 工作区,然后枚举工作区中的模型,并在控制台窗口中显示其名称。

using System;
using Microsoft.AnalysisServices.Tabular;

class Program {
  static void Main() {

    // create the connect string
    string workspaceConnection = "powerbi://api.powerbi.com/v1.0/myorg/LearningTOM";
    string connectString = $"DataSource={workspaceConnection};";

    // connect to the Power BI workspace referenced in connect string
    Server server = new Server();
    server.Connect(connectString);

    // enumerate through models in workspace to display their names
    foreach (Database database in server.Databases) {
      Console.WriteLine(database.Name);
    }
  }
}

在此示例中,连接字符串包含工作区连接 URL,但不包含有关用户的信息。 如果使用此代码运行控制台应用程序,应用程序将开始运行,然后系统会提示你使用基于浏览器的窗口登录。 如果使用有权访问工作区连接 URL 所引用的工作区的用户帐户登录,则 TOM 库能够获取访问令牌、连接到 Power BI 服务,并通过工作区中的模型枚举。

若要详细了解如何通过 XMLA 终结点进行连接,请参阅 Sematic 模型与 XMLA 终结点的连接 - 连接到高级工作区

使用用户名和密码进行身份验证

对于安全性不重要的开发和测试方案,可以硬编码用户名和密码,无需在每次运行程序以交互方式登录以测试代码时,如以下代码所示:

string workspaceConnection = "powerbi://api.powerbi.com/v1.0/myorg/YOUR_WORKSPACE";
string userId = "YOUR_USER_NAME";
string password = "YOUR_USER_PASSWORD";
string connectStringUser = $"DataSource={workspaceConnection};User ID={userId};Password={password};";
server.Connect(connectStringUser);

使用服务主体进行身份验证

作为服务主体而不是用户进行身份验证也很容易。 如果已创建具有应用程序 ID 和应用程序机密的Microsoft Entra应用程序,则可以使用以下代码示例对代码进行身份验证,以作为Microsoft Entra应用程序的服务主体运行:

string workspaceConnection = "powerbi://api.powerbi.com/v1.0/myorg/YOUR_WORKSPACE";
string tenantId = "YOUR_TENANT_ID";
string appId = "YOUR_APP_ID";
string appSecret = "YOUR_APP_SECRET";
string connectStringApp = $"DataSource={workspaceConnection};User ID=app:{appId}@{tenantId};Password={appSecret};";
server.Connect(connectStringApp);

若要使用 TOM 进行编程并作为服务主体访问模型,必须在 Power BI 管理员门户中配置租户级 Power BI 设置。 使用服务主体 和应用程序机密嵌入 Power BI 内容中介绍了配置 Power BI 以支持作为服务主体进行连接的步骤。

使用Microsoft Entra访问令牌进行身份验证

使用有效的Microsoft Entra访问令牌建立连接时,TOM 还提供灵活性。 如果你具备使用 Microsoft Entra ID 实现身份验证流并获取访问令牌的开发人员技能,则可以在不使用用户名的情况下设置 TOM 连接字符串的格式,但应将访问令牌作为密码包含在内,如以下代码示例所示:

public static void ConnectToPowerBIAsUser() {
  string workspaceConnection = "powerbi://api.powerbi.com/v1.0/myorg/YOUR_WORKSPACE";
  string accessToken = TokenManager.GetAccessToken();  // you must implement GetAccessToken yourself
  string connectStringUser = $"DataSource={workspaceConnection};Password={accessToken};";
  server.Connect(connectStringUser);
}

如果要获取基于用户的访问令牌以使用 TOM 连接到 Power BI 工作区,请确保在获取访问令牌时请求以下委派权限,以确保拥有所需的所有创作权限:

public static readonly string[] XmlaScopes = new string[] {
    "https://analysis.windows.net/powerbi/api/Content.Create",
    "https://analysis.windows.net/powerbi/api/Dataset.ReadWrite.All",
    "https://analysis.windows.net/powerbi/api/Workspace.ReadWrite.All",
};

如果一直在使用 Power BI REST API 编程,则可以识别出熟悉的权限,例如 Content.CreateDataset.ReadWrite.AllWorkspace.ReadWrite.All。 一个有趣的观察是,TOM 使用与 在 Microsoft Entra 资源 ID https://analysis.windows.net/powerbi/api范围内定义的 Power BI REST API 相同的委派权限集。

XMLA 终结点和 Power BI REST API 共享同一组委派权限这一事实具有其优点。 可以在 TOM 和 Power BI REST API 之间互换使用访问令牌。 获取访问令牌以调用 TOM 以创建新模型后,可以使用同一访问令牌调用 Power BI REST API 来设置数据源凭据,如本文稍后所述。

使 Power BI 程序员感到困惑的一件事是,服务主体不使用委托的权限。 相反,在使用 TOM 编程时,可以通过将服务主体作为管理员或成员角色的成员添加到目标工作区来配置服务主体的访问权限。

了解服务器、数据库和模型对象

TOM 中的对象模型基于具有顶级 Server 对象的层次结构,该对象包含 Database 对象的集合。 在 Power BI 中使用 TOM 进行编程时, Server 对象表示 Power BI 工作区, Database 对象表示 Power BI 模型。

包含所有对象的表格对象模型关系图

每个 数据库 都包含一个 Model 对象,该对象提供对数据模型的读/写访问权限。 模型包含数据模型元素的集合,包括 DataSource关系透视区域性角色

Hello World代码所示,调用服务器后。连接后,可以通过枚举 Server 对象的 Databases 集合,轻松发现 Power BI 工作区中存在的模型,如以下代码所示:

foreach (Database database in server.Databases) {
    Console.WriteLine(database.Name);
}

还可以使用由 Databases 集合对象公开的 GetByName 方法按名称访问模型,如下所示:

Database database = server.Databases.GetByName("Wingtip Sales");

区分 Database对象及其内部 Model 属性非常重要。 可以使用 数据库 对象属性来发现模型属性,例如 名称IDCompatibilityModeCompatibilityLevel。 还有一个 EstimatedSize 属性,可以发现模型的增长大小。 其他属性包括 LastUpdateLastProcessedLastSchemaUpdate ,可用于确定上次刷新基础模型的时间以及上次更新模型架构的时间。

public static void GetDatabaseInfo(string DatabaseName) {
  Database database = server.Databases.GetByName(DatabaseName);
  Console.WriteLine("Name: " + database.Name);
  Console.WriteLine("ID: " + database.ID);
  Console.WriteLine("CompatibilityMode: " + database.CompatibilityMode);
  Console.WriteLine("CompatibilityLevel: " + database.CompatibilityLevel);
  Console.WriteLine("EstimatedSize: " + database.EstimatedSize);
  Console.WriteLine("LastUpdated: " + database.LastUpdate);
  Console.WriteLine("LastProcessed: " + database.LastProcessed);
  Console.WriteLine("LastSchemaUpdate: " + database.LastSchemaUpdate);
}

虽然 Database 对象有自己的属性,但它是 Database 对象的内部 Model 对象,它使你能够读取和写入模型的基础数据模型。 下面是对数据库 Model 对象进行编程以枚举其 Tables 集合并发现其中的表的简单示例。

在 TOM 对象模型中,每个 Table 对象都有其分区的集合对象。 列、度量值和层次结构。

包含表、分区、列、度量值和层次结构的表格对象模型图

检索数据库的Model 对象后,可以使用 Tables 集合的 Find 方法按名称访问模型中的特定。 以下示例检索名为 Sales 的表,并通过 Columns 集合和 Measures 集合枚举来发现其成员:

Model databaseModel = server.Databases.GetByName("Tom Demo").Model;

Table tableSales = databaseModel.Tables.Find("Sales");

foreach (Column column in tableSales.Columns) {
  Console.WriteLine("Coulumn: " + column.Name);
}

foreach (Measure measure in tableSales.Measures) {
  Console.WriteLine("Measure: " + measure.Name);
  Console.WriteLine(measure.Expression);
}

使用 TOM 修改模型

在上面的部分中,你已了解如何访问 Database 对象及其 Model 对象,以检查 Power BI 服务中运行的模型的数据模型。 现在,可以通过向表添加度量值,使用 TOM 对第一个模型更新进行编程。

必须 为 XMLA 读写启用正在使用的容量。 默认情况下,XMLA 终结点权限设置设置为“读取”,因此必须显式将其设置为“由具有容量管理员权限的人员读取写入”。 可以在管理员门户“容量设置”页中查看和更新此设置。

管理员门户中的 XMLA 读写设置。

将 XMLA 终结点配置为读写后,可以将名为 Sales Revenue 的新度量值添加到 Sales 表,如以下代码所示:

Model dataset = server.Databases.GetByName("Tom Demo Starter").Model;
Table tableSales = dataset.Tables.Find("Sales");
Measure salesRevenue = new Measure();
salesRevenue.Name = "Sales Revenue";
salesRevenue.Expression = "SUM(Sales[SalesAmount])";
salesRevenue.FormatString = "$#,##0.00";
tableSales.Measures.Add(salesRevenue);
dataset.SaveChanges();

让我们更详细地了解一下此代码。 首先,使用 C# new 运算符创建新的 Measure 对象,并为 NameExpressionFormatString 提供值。 然后,通过调用 Add 方法,将新的 Measure 对象添加到目标 Table 对象的 Measure 集合。 最后,调用 Model 对象的 SaveChanges 方法,将更改写回到 Power BI 服务中的模型。

请记住,在调用 SaveChanges 之前,对模型的更新在内存中批处理。 假设你希望隐藏表中的每一列。 首先,可以编写 foreach 循环以枚举表的所有 Column 对象,并将每个 Column 对象的 IsHidden 属性设置为 true。 foreach 循环完成后,内存中会进行多个列更新批处理。 但最后一次调用 SaveChanges 会将所有更改批量推送回 Power BI 服务,如下所示:

Model dataset = server.Databases.GetByName("Tom Demo").Model;
Table tableSales = dataset.Tables.Find("Sales");

foreach (Column column in tableSales.Columns) {
  column.IsHidden = true;
}

dataset.SaveChanges();

假设你想要更新现有列的 FormatString 属性。 Columns 集合公开 Find 方法以检索目标 Column 对象。 之后,只需设置 FormatString 属性并调用 SaveChanges,如下所示:

Model dataset = server.Databases.GetByName("Tom Demo").Model;
Table tableSales = dataset.Tables.Find("Products");
Column columnListPrice = tableSales.Columns.Find("List Price");
columnListPrice.FormatString = "$#,##0.00";
dataset.SaveChanges();

TOM 能够动态发现模型内的内容,从而有机会以通用和全面的方式执行更新。 假设你正在管理一个模型,该模型包含大量基于 DateTime 数据类型的表和数十个甚至数百个列。 可以使用以下命令在同一位置更新整个模型中每个 DateTime 列的 FormatString 属性:

Database database = server.Databases.GetByName("Tom Demo Starter");
Model datasetModel = database.Model;

foreach (Table table in datasetModel.Tables) {
  foreach (Column column in table.Columns) {
    if(column.DataType == DataType.DateTime) {
      column.FormatString = "yyyy-MM-dd";
    }
  }
}

datasetModel.SaveChanges();

使用 TOM 刷新模型

现在,让我们执行典型的模型维护操作。 如以下代码所示,使用 TOM 启动模型刷新操作并不复杂:

public static void RefreshDatabaseModel(string Name) {
  Database database = server.Databases.GetByName(Name);
  database.Model.RequestRefresh(RefreshType.DataOnly);
  database.Model.SaveChanges();
}

与手动刷新和计划模型刷新一样,通过 XMLA 终结点刷新显示在 刷新历史记录中,但标签为 “通过 XMLA 终结点”。

“刷新历史记录”对话框

注意

虽然 TOM 提供启动刷新操作的功能,但它无法为 Power BI 模型设置 数据源凭据 。 若要使用 TOM 刷新模型,必须先在 语义模型设置 或使用 Power BI REST API 中设置数据源凭据。

创建和克隆模型

假设你需要使用用 C# 编写的代码创建和克隆 Power BI 模型。 首先,我们编写一个名为 CreateDatabase 的可重用函数,用于创建新的 Database 对象,如下所示:

public static Database CreateDatabase(string DatabaseName) {

  string newDatabaseName = server.Databases.GetNewName(DatabaseName);
  var database = new Database() {
    Name = newDatabaseName,
    ID = newDatabaseName,
    CompatibilityLevel = 1520,
    StorageEngineUsed = Microsoft.AnalysisServices.StorageEngineUsed.TabularMetadata,
    Model = new Model() {
      Name = DatabaseName + "-Model",
      Description = "A Demo Tabular data model with 1520 compatibility level."
    }
  };

  server.Databases.Add(database);
  database.Update(Microsoft.AnalysisServices.UpdateOptions.ExpandFull);
  return database;

}

在此示例中,我们将首先使用 Databases 集合对象的 GetNewName方法,以确保新模型名称在目标工作区中是唯一的。 之后,可以使用 C# new 运算符创建 Database 对象及其 Model 对象,如下面的代码所示。 最后,此方法将新的 Database 对象添加到 Databases 集合并调用 数据库。更新 方法。

如果目标是复制现有模型而不是创建新模型,则可以使用以下 CopyDatabase 方法克隆 Power BI 模型,方法是创建新的空模型,然后在源模型的 Model 对象上调用 CopyTo,将整个数据模型复制到新创建的模型中。

public static Database CopyDatabase(string sourceDatabaseName, string DatabaseName) {
  Database sourceDatabase = server.Databases.GetByName(sourceDatabaseName);
  string newDatabaseName = server.Databases.GetNewName(DatabaseName);
  Database targetDatabase = CreateDatabase(newDatabaseName);
  sourceDatabase.Model.CopyTo(targetDatabase.Model);
  targetDatabase.Model.SaveChanges();
  targetDatabase.Model.RequestRefresh(RefreshType.Full);
  targetDatabase.Model.SaveChanges();
  return targetDatabase;
}

从头开始创建真实模型

好吧,现在假设你刚刚从头开始创建了一个新模型,现在需要使用 TOM 通过添加表、列、度量值、层次结构和表关系来编写实际数据模型。 让我们看一个代码示例,该示例创建一个包含已定义列的新表,添加三级维度层次结构,甚至为基础表查询提供 M 表达式:

private static Table CreateProductsTable() {

  Table productsTable = new Table() {
    Name = "Products",
    Description = "Products table",
    Partitions = {
      new Partition() {
        Name = "All Products",
        Mode = ModeType.Import,
        Source = new MPartitionSource() {
          // M code for query maintained in separate source file
          Expression = Properties.Resources.ProductQuery_m
        }
      }
    },
    Columns = {
      new DataColumn() { Name = "ProductId", DataType = DataType.Int64, SourceColumn = "ProductId", IsHidden = true },
      new DataColumn() { Name = "Product", DataType = DataType.String, SourceColumn = "Product" },
      new DataColumn() { Name = "Description", DataType = DataType.String, SourceColumn = "Description" },
      new DataColumn() { Name = "Category", DataType = DataType.String, SourceColumn = "Category" },
      new DataColumn() { Name = "Subcategory", DataType = DataType.String, SourceColumn = "Subcategory" },
      new DataColumn() { Name = "Product Image", DataType = DataType.String, 
                        SourceColumn = "ProductImageUrl", DataCategory = "ImageUrl" }
     }
  };

  productsTable.Hierarchies.Add(
    new Hierarchy() {
      Name = "Product Category",
      Levels = {
        new Level() { Ordinal=0, Name="Category", Column=productsTable.Columns["Category"] },
        new Level() { Ordinal=1, Name="Subcategory", Column=productsTable.Columns["Subcategory"] },
        new Level() { Ordinal=2, Name="Product", Column=productsTable.Columns["Product"] }
      }
  });

  return productsTable;
}

创建一组帮助程序方法来创建表后,可以将它们组合在一起以创建数据模型,如下所示:

Model model = database.Model;
Table tableCustomers = CreateCustomersTable();
Table tableProducts = CreateProductsTable();
Table tableSales = CreateSalesTable();
Table tableCalendar = CreateCalendarTable();
model.Tables.Add(tableCustomers);
model.Tables.Add(tableProducts);
model.Tables.Add(tableSales);
model.Tables.Add(tableCalendar);

TOM 在 Model 对象上公开一个 Relationships 集合,使用该集合可以定义模型中表之间的关系。 下面是创建 SingleColumnRelationship 对象所需的代码,该对象在 Products 表和 Sales 表之间建立一对多关系:

model.Relationships.Add(new SingleColumnRelationship {
  Name = "Products to Sales",
  ToColumn = tableProducts.Columns["ProductId"],
  ToCardinality = RelationshipEndCardinality.One,
  FromColumn = tableSales.Columns["ProductId"],
  FromCardinality = RelationshipEndCardinality.Many
});

添加完表和表关系后,通过调用模型保存工作 。SaveChanges

model.SaveChanges();

此时,调用 SaveChanges 后,应能够看到在 Power BI 服务中创建的新模型,并开始使用它创建新报表。

Power BI 服务中的模型报表。

重要

请记住,必须先在语义模型设置中或通过 Power BI REST API 指定数据源凭据,然后才能刷新模型。

示例项目

此处提供了本文中介绍的 C# 代码的示例项目。 现在,你可以开始使用 TOM 进行编程,并找到在 Power BI 自定义解决方案开发中利用这个功能强大的新 API 的方法。

另请参阅

语义模型与 XMLA 终结点的连接
排查 XMLA 终结点连接问题