Programación de modelos semánticos de Power BI con el modelo de objetos tabulares (TOM)

Se aplica a: SQL Server 2016 y versiones posteriores analysis Services Azure Analysis Services Fabric/Power BI Premium

Este artículo fue creado originalmente por el equipo de asesoramiento al cliente (CAT) de Power BI para El campamento de desarrollo de Power BI, una colección de sesiones, artículos y vídeos sobre la programación avanzada para Power BI.

Power BI Premium modelos semánticos incluyen el punto de conexión XMLA. El punto de conexión es importante para los desarrolladores de Power BI, ya que proporciona API para interactuar con el motor de Analysis Services que se ejecuta en el servicio Power BI y para programar directamente con los modelos de Power BI. Un número creciente de profesionales de Power BI ha descubierto que pueden crear, ver y administrar modelos de Power BI mediante herramientas preexistentes que usan el protocolo XMLA, como SQL Server Management Studio, Editor tabular y DAX Studio. Como desarrollador de .NET, ahora puede escribir código de C# en una aplicación .NET para crear y modificar modelos directamente en el servicio Power BI.

El modelo de objetos tabulares (TOM) es una biblioteca de .NET que proporciona una capa abstracta sobre el punto de conexión XMLA. Permite a los desarrolladores escribir código en términos de un modelo de programación intuitivo que incluye clases como Model, Table, Column y Measure. En segundo plano, TOM traduce las operaciones de lectura y escritura en el código en solicitudes HTTP ejecutadas en el punto de conexión XMLA.

Diagrama de la aplicación que se va a modelar a través del punto de conexión XMLA.

El objetivo de este artículo es empezar a trabajar con TOM y demostrar cómo escribir el código de C# necesario para crear y modificar modelos mientras se ejecutan en el servicio Power BI. Sin embargo, TOM también se puede usar en escenarios que no implican el punto de conexión XMLA, como al programar con un modelo local que se ejecuta en Power BI Desktop. Para más información sobre el uso de TOM con Power BI Desktop, consulte la serie de blog de Phil Seamark, miembro de CAT de Power BI, y asegúrese de watch el vídeo How to program datasets using the Tabular Object Model (TOM) (Cómo programar conjuntos de datos mediante el modelo de objetos tabulares (TOM) del Campo de desarrollo de Power BI.

TOM representa una API nueva y eficaz para los desarrolladores de Power BI que es independiente y distinta de las API REST de Power BI. Aunque hay cierta superposición entre estas dos API, cada una de estas API incluye una cantidad significativa de funcionalidad no incluida en la otra. Además, hay escenarios que requieren que un desarrollador use ambas API juntas para implementar una solución completa.

Introducción con el modelo de objetos tabulares

Lo primero que necesita para poder programar con TOM es la dirección URL de una conexión de área de trabajo. La dirección URL de conexión del área de trabajo hace referencia a un área de trabajo específica y se usa para crear un cadena de conexión que permita que el código se conecte a ese área de trabajo de Power BI y a los modelos que se ejecutan dentro. Para empezar, vaya a la página Configuración de un área de trabajo de Power BI que se ejecuta en una capacidad dedicada.

Vínculo a configuración del área de trabajo.

Nota:

El punto de conexión XMLA solo se admite para los modelos que se ejecutan en una capacidad dedicada. No está disponible para los modelos que se ejecutan en una capacidad compartida. Si trabaja con modelos en una capacidad de Power BI Premium por usuario, puede conectarse como usuario, pero no puede conectarse como entidad de servicio.

Una vez que vaya a la pestaña Premium del panel Configuración , copie la dirección URL de conexión del área de trabajo en el Portapapeles.

Área de trabajo cadena de conexión en configuración del modelo semántico.

El siguiente paso consiste en crear una nueva aplicación .NET en la que se escribe el código de C# que programa con TOM. Puede crear una aplicación web o una aplicación de escritorio mediante .NET 5, .NET Core 3.1 o versiones anteriores en .NET Framework. En este artículo se crea una aplicación de consola de C# sencilla mediante el SDK de .NET 5.

Creación de una nueva aplicación de consola

Empiece por usar la CLI de .NET para crear una nueva aplicación de consola.

dotnet new console --name`

Adición del paquete NuGet del modelo de objetos tabulares

Después de crear la aplicación de consola, agregue el paquete NuGet Microsoft.AnalysisServices.AdomdClient.NetCore.retail.amd64 que contiene el modelo de objetos tabulares (TOM). Puede instalar el paquete en una aplicación de .NET 5 mediante la siguiente CLI de .NET:

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

Incorporación de la cadena de conexión

Cuando el proyecto tenga instalado el paquete NuGet con la biblioteca TOM, puede crear la aplicación Hola mundo tradicional con TOM. La aplicación se conecta a un área de trabajo de Power BI mediante la dirección URL de conexión del área de trabajo y, a continuación, enumera los modelos del área de trabajo y muestra sus nombres en la ventana de la consola.

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);
    }
  }
}

En este ejemplo, el cadena de conexión contiene la dirección URL de conexión del área de trabajo, pero no hay información sobre el usuario. Si ejecuta la aplicación de consola con este código, la aplicación comenzará a ejecutarse y, a continuación, se le pedirá una ventana basada en explorador para iniciar sesión. Si inicia sesión con una cuenta de usuario que tiene permisos para acceder al área de trabajo a la que hace referencia la dirección URL de conexión del área de trabajo, la biblioteca TOM puede adquirir un token de acceso, conectarse al servicio Power BI y enumerar a través de los modelos del área de trabajo.

Para más información sobre cómo conectarse a través del punto de conexión XMLA, consulte Conectividad del modelo sematic con el punto de conexión XMLA: conexión a un área de trabajo Premium.

Autenticación con nombre de usuario y contraseña

En escenarios de desarrollo y pruebas en los que la seguridad no es tan importante, puede codificar de forma rígida el nombre de usuario y la contraseña, lo que elimina la necesidad de iniciar sesión de forma interactiva cada vez que ejecute un programa para probar el código, como se muestra en el código siguiente:

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);

Autenticación con una entidad de servicio

También es bastante fácil autenticarse como entidad de servicio en lugar de como usuario. Si ha creado una aplicación de Microsoft Entra con un identificador de aplicación y un secreto de aplicación, puede autenticar el código para ejecutarse como entidad de servicio para la aplicación de Microsoft Entra mediante el ejemplo de código siguiente:

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);

Para programar con TOM y acceder a un modelo como entidad de servicio, debe configurar una configuración de Power BI de nivel de inquilino en el portal de Power BI Administración. Los pasos para configurar Power BI para admitir la conexión como entidad de servicio se describen en Inserción de contenido de Power BI con la entidad de servicio y un secreto de aplicación.

Autenticación con un token de acceso de Microsoft Entra

TOM también proporciona flexibilidad al establecer una conexión mediante un token de acceso de Microsoft Entra válido. Si tiene las aptitudes para desarrolladores para implementar un flujo de autenticación con Microsoft Entra id. y adquirir tokens de acceso, puede dar formato al cadena de conexión de TOM sin un nombre de usuario, pero incluir el token de acceso como contraseña en su lugar, como se muestra en el ejemplo de código siguiente:

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);
}

Si va a adquirir un token de acceso basado en el usuario para conectarse a un área de trabajo de Power BI con TOM, asegúrese de solicitar los siguientes permisos delegados al adquirir el token de acceso para asegurarse de que tiene todos los permisos de creación que necesita:

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",
};

Si ha estado programando con la API REST de Power BI, es posible que reconozca permisos conocidos como Content.Create, Dataset.ReadWrite.All y Workspace.ReadWrite.All. Una observación interesante es que TOM usa el mismo conjunto de permisos delegados que la API REST de Power BI definida dentro del ámbito del identificador de recurso de Microsoft Entra de https://analysis.windows.net/powerbi/api.

El hecho de que tanto el punto de conexión XMLA como la API REST de Power BI compartan el mismo conjunto de permisos delegados tienen sus ventajas. Los tokens de acceso se pueden usar indistintamente entre TOM y la API REST de Power BI. Una vez que haya adquirido un token de acceso para llamar a TOM para crear un nuevo modelo, puede usar el mismo token de acceso para llamar a la API REST de Power BI para establecer las credenciales del origen de datos, como se describe más adelante en este artículo.

Una cosa que tiende a confundir a los programadores de Power BI es que las entidades de servicio no usan permisos delegados. En su lugar, al programar con TOM, configure el acceso para una entidad de servicio agregándolo al área de trabajo de destino como miembro en el rol de Administración o Miembro.

Descripción de los objetos de servidor, base de datos y modelo

El modelo de objetos de TOM se basa en una jerarquía con el objeto Server de nivel superior que contiene una colección de objetos Database . Al programar con TOM en Power BI, el objeto Server representa un área de trabajo de Power BI y el objeto Database representa un modelo de Power BI.

Diagrama del modelo de objetos tabulares con todos los objetos

Cada base de datos contiene un objeto Model que proporciona acceso de lectura y escritura al modelo de datos. El modelo contiene colecciones para los elementos de un modelo de datos, incluidos DataSource, Table, Relationship, Perspective, Culture y Role.

Como se muestra en el código de Hola mundo, una vez que llame al servidor. Conéctese para detectar fácilmente qué modelos existen dentro de un área de trabajo de Power BI mediante la enumeración a través de la colección Databases del objeto Server, como se muestra en el código siguiente:

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

También puede usar el método GetByName expuesto por el objeto de colección Databases para tener acceso a un modelo por nombre, de la siguiente manera:

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

Es importante distinguir entre un objeto Databasey su propiedad Model interna. Puede usar las propiedades del objeto Database para detectar atributos de modelo como Name, ID, CompatibilityMode y CompatibilityLevel. También hay una propiedad EstimatedSize que permite descubrir el tamaño de un modelo. Otras propiedades incluyen LastUpdate, LastProcessed y LastSchemaUpdate , que permiten determinar cuándo se actualizó por última vez el modelo subyacente y cuándo se actualizó por última vez el esquema del modelo.

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);
}

Aunque el objeto Database tiene sus propias propiedades, es el objeto Model interno de un objeto Database que proporciona la capacidad de leer y escribir en el modelo de datos subyacente de un modelo. Este es un ejemplo sencillo de programación del objeto Model de base de datos para enumerar a través de su colección Tables y descubrir qué tablas hay dentro.

En el modelo de objetos TOM, cada objeto Table tiene objetos de colección para sus particiones. columnas, medidas y jerarquías.

Diagrama del modelo de objetos tabulares con tabla, partición, columna, medida y jerarquía

Una vez que haya recuperado el objeto Model para una base de datos, puede tener acceso a una tabla específica por su nombre en el modelo mediante el método Find de la colección Tables . Este es un ejemplo de recuperación de una tabla denominada Sales y la detección de sus miembros mediante la enumeración de la colección Columns y la colección 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);
}

Modificación de modelos con TOM

En las secciones anteriores, ha visto cómo obtener acceso a un objeto Database y a su objeto Model para inspeccionar el modelo de datos de un modelo que se ejecuta en el servicio Power BI. Ahora es el momento de programar la primera actualización del modelo con TOM agregando una medida a una tabla.

La capacidad que usa debe estar habilitada para lectura y escritura XMLA. De forma predeterminada, la configuración de permisos de punto de conexión XMLA se establece en Lectura, por lo que debe establecerse explícitamente en Lectura de escritura por parte de alguien con permisos de capacidad Administración. Esta configuración se puede ver y actualizar en la página Configuración de capacidad del portal de Administración.

Configuración de lectura y escritura de XMLA en el portal de Administración.

Cuando el punto de conexión XMLA se haya configurado para lectura y escritura, puede agregar una nueva medida denominada Ingresos de ventas a la tabla Sales , como se muestra en el código siguiente:

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();

Echemos un vistazo más de cerca a este código. En primer lugar, cree un nuevo objeto Measure con el operador nuevo de C# y proporcione valores para Name, Expression y FormatString. A continuación, agregue el nuevo objeto Measure a la colección Measures del objeto Table de destino llamando al método Add . Por último, llame al método SaveChanges del objeto Model para volver a escribir los cambios en el modelo en el servicio Power BI.

Tenga en cuenta que las actualizaciones de un modelo se procesan por lotes en memoria hasta que se llama a SaveChanges. Imagine un escenario en el que desea ocultar todas las columnas de una tabla. Puede empezar escribiendo un bucle foreach para enumerar todos los objetos Column de una tabla y estableciendo la propiedad IsHidden para cada objeto Column en true. Una vez completado el bucle foreach , tiene varias actualizaciones de columnas que se procesan por lotes en memoria. Pero es la llamada final a SaveChanges que vuelve a insertar todos los cambios en el servicio Power BI en un lote, como se muestra a continuación:

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();

Supongamos que desea actualizar la propiedad FormatString para una columna existente. La colección Columns expone un método Find para recuperar el objeto Column de destino. Después, es cuestión de establecer la propiedad FormatString y llamar a SaveChanges, como se muestra a continuación:

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();

La capacidad de TOM para descubrir dinámicamente lo que hay dentro de un modelo proporciona oportunidades para realizar actualizaciones de forma genérica y barrida. Imagine un escenario en el que se administra un modelo que tiene muchas tablas y decenas o incluso cientos de columnas basadas en el tipo de datos DateTime . Puede actualizar la propiedad FormatString para cada columna DateTime del modelo completo al mismo mediante lo siguiente:

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();

Actualización de modelos con TOM

Ahora vamos a realizar una operación típica de mantenimiento del modelo. Como ve en el código siguiente, no es muy complicado iniciar una operación de actualización del modelo mediante TOM:

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

Al igual que con la actualización manual y programada del modelo, las actualizaciones a través del punto de conexión XMLA se muestran en el historial de actualizaciones, pero con la etiqueta , Mediante punto de conexión XMLA.

Cuadro de diálogo Actualizar historial

Nota:

Aunque TOM proporciona la capacidad de iniciar una operación de actualización, no puede establecer credenciales de origen de datos para un modelo de Power BI. Para actualizar los modelos con TOM, primero debe establecer las credenciales del origen de datos en la configuración del modelo semántico o mediante las API rest de Power BI.

Creación y clonación de modelos

Imagine que tiene un requisito para crear y clonar modelos de Power BI mediante código escrito en C#. Comencemos escribiendo una función reutilizable denominada CreateDatabase que crea un nuevo objeto Database , como este:

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;

}

En este ejemplo, comenzaremos usando el método GetNewNamedel objeto de colección Databases para asegurarnos de que el nuevo nombre del modelo es único en el área de trabajo de destino. Después, el objeto Database y su objeto Model se pueden crear mediante el operador new de C# como se muestra en el código siguiente. Al final, este método agrega el nuevo objeto Database a la colección Databases y llama a la base de datos. Método Update .

Si el objetivo es copiar un modelo existente en lugar de crear uno nuevo, puede usar el siguiente método CopyDatabase para clonar un modelo de Power BI mediante la creación de un nuevo modelo vacío y, a continuación, llamar a CopyTo en el objeto Model para que el modelo de origen copie todo el modelo de datos en el modelo recién creado.

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;
}

Creación de un modelo real desde cero

Ahora imagine que acaba de crear un nuevo modelo desde cero y ahora necesita usar TOM para crear un modelo de datos real mediante la adición de tablas, columnas, medidas, jerarquías y relaciones de tabla. Echemos un vistazo a un ejemplo de código que crea una nueva tabla que incluye columnas definidas, agrega una jerarquía dimensional de tres niveles e incluso proporciona la expresión M para la consulta de tabla subyacente:

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;
}

Una vez que haya creado un conjunto de métodos auxiliares para crear las tablas, puede crearlas juntas para crear un modelo de datos, de la siguiente manera:

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 expone una colección Relationships en el objeto Model que permite definir las relaciones entre las tablas del modelo. Este es el código necesario para crear un objeto SingleColumnRelationship que establece una relación uno a varios entre la tabla Products y la tabla Sales :

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

Cuando haya terminado de agregar las tablas y la relación de tabla, guarde el trabajo con una llamada al modelo. SaveChanges:

model.SaveChanges();

En este momento, después de llamar a SaveChanges, debería poder ver el nuevo modelo creado en el servicio Power BI y empezar a usarlo para crear nuevos informes.

Informe de modelo en el servicio Power BI.

Importante

Recuerde que debe especificar las credenciales del origen de datos en la configuración del modelo semántico o a través de la API rest de Power BI para poder actualizar el modelo.

Proyecto de ejemplo

El proyecto de ejemplo con el código de C# que ha visto en este artículo está disponible aquí. Ahora es el momento de empezar a programar con TOM y encontrar formas de aprovechar esta nueva API eficaz en el desarrollo de soluciones personalizadas para Power BI.

Consulte también

Conectividad del modelo semántico con el punto de conexión XMLA
Solución de problemas de conectividad de los puntos de conexión XMLA