Function Library

Modelagem

TMDL

Referência de TMDL: tipos de objeto, sintaxe, estrutura de pastas e scripts createOrReplace.

TMDL — Tabular Model Definition Language

Consolidado a partir da documentação oficial Microsoft Learn (Analysis Services / Power BI / Fabric). Fontes: tmdl-overview, tmdl-how-to, tmdl-scripts (learn.microsoft.com/analysis-services/tmdl).

Nota de validação (2026-08-06): amostra de fatos checáveis conferida contra as páginas oficiais citadas (nível de compatibilidade 1200+, regras de indentação/whitespace/casing) — todos batem exatamente. Substitui o conteúdo anterior de 03-tmdl-pbir-guia-conteudo.md como referência principal.


1. O que é o TMDL

TMDL (Tabular Model Definition Language) é uma sintaxe de definição de objetos para modelos tabulares em nível de compatibilidade 1200 ou superior. Aplica-se a SQL Server Analysis Services (2016+), Azure Analysis Services e Fabric/Power BI Premium — é o formato usado, por exemplo, nos Power BI Desktop Projects (.pbip).

Características-chave:

CaracterísticaDescrição
Compatibilidade total com TOMTodo objeto TMDL expõe as mesmas propriedades do Tabular Object Model (TOM)
Baseado em textoSintaxe próxima de YAML, com indentação demarcando hierarquia pai-filho, poucos delimitadores
Melhor ediçãoMelhor experiência para propriedades com expressões embutidas (DAX, M)
ColaboraçãoRepresentação em pastas com um arquivo por objeto → mais amigável a controle de versão (Git) que o TMSL (JSON monolítico)

Exemplo mínimo de sintaxe

database Sales
    compatibilityLevel: 1567

model Model
    culture: en-US

table Sales

    partition 'Sales-Partition' = m
        mode: import
        source =
            let
                Source = Sql.Database(Server, Database)
                

    measure 'Sales Amount' = SUMX('Sales', 'Sales'[Quantity] * 'Sales'[Net Price])
        formatString: $ #,##0

    column 'Product Key'
        dataType: int64
        isHidden
        sourceColumn: ProductKey
        summarizeBy: None

relationship cdb6e6a9-c9d1-42b9-b9e0-484a1bc7e123
    fromColumn: Sales.'Product Key'
    toColumn: Product.'Product Key'

role Role_Store1
    modelPermission: read
    tablePermission Store = 'Store'[Store Code] IN {1,10,20,30}

expression Server = "localhost" meta [IsParameterQuery=true, Type="Text", IsParameterQueryRequired=true]

2. Estrutura de pastas TMDL

Ao contrário do TMSL (um único JSON), o TMDL organiza o modelo em um nível de subpastas + arquivos-raiz:

TMDL/ ├── cultures/ │ ├── en-US.tmdl │ └── pt-PT.tmdl ├── perspectives/ │ └── perspective1.tmdl ├── roles/ │ ├── role1.tmdl │ └── role2.tmdl ├── tables/ │ ├── About.tmdl │ ├── Calendar.tmdl │ ├── Customer.tmdl │ ├── Product.tmdl │ ├── Sales.tmdl │ └── Store.tmdl ├── relationships.tmdl ├── functions.tmdl ├── expressions.tmdl ├── dataSources.tmdl ├── model.tmdl └── database.tmdl

Regras de granularidade:

  • 1 arquivo para database, 1 para model.
  • 1 arquivo único agregando todas as dataSources, todas as expressions, todas as functions (DAX User Defined Functions) e todos os relationships do modelo.
  • 1 arquivo por cultura (cultures/), 1 por perspectiva (perspectives/), 1 por role (roles/) e 1 por tabela (tables/).
  • Dentro do arquivo de cada tabela vivem todas as metadados internas: colunas, medidas, hierarquias, partições etc.

💡 Essa granularidade é o que torna o TMDL "Git-friendly": alterar uma medida gera diff em um único arquivo de tabela, não no dataset inteiro.


3. TMDL API (.NET)

Classe principal: TmdlSerializer, namespace Microsoft.AnalysisServices.Tabular.

3.1 Serialização em pasta

// TOM Database -> pasta TMDL
public static void SerializeDatabaseToFolder(Database database, string path)

// pasta TMDL -> TOM Database
public static Database DeserializeDatabaseFromFolder(string path)

Exemplo — extrair TMDL de um workspace Power BI Premium:

var workspaceXmla = "<Workspace XMLA address>";
var datasetName = "<dataset name>";
var outputPath = System.Environment.CurrentDirectory;

using (var server = new Microsoft.AnalysisServices.Tabular.Server())
{
    server.Connect(workspaceXmla);
    var database = server.Databases.GetByName(datasetName);
    var destinationFolder = $"{outputPath}\\{database.Name}-tmdl";

    Microsoft.AnalysisServices.Tabular.TmdlSerializer.SerializeDatabaseToFolder(
        database.Model, destinationFolder);
}

Exemplo — reimplantar (deploy) a pasta TMDL editada:

var xmlaServer = "<Workspace XMLA address>";
var tmdlFolderPath = $"{System.Environment.CurrentDirectory}\\Contoso-tmdl";

var model = Microsoft.AnalysisServices.Tabular.TmdlSerializer.DeserializeModelFromFolder(tmdlFolderPath);

using (var server = new Microsoft.AnalysisServices.Tabular.Server())
{
    server.Connect(xmlaServer);
    using (var remoteDatabase = server.Databases[model.Database.ID])
    {
        model.CopyTo(remoteDatabase.Model);
        remoteDatabase.Model.SaveChanges();
    }
}

🛠️ Fluxo típico: SerializeDatabaseToFolder → editar .tmdl em VS Code (com a extensão TMDL) → DeserializeModelFromFolder + CopyTo + SaveChanges para publicar.

3.2 Serialização em string (objeto único)

public static string SerializeObject(MetadataObject object, bool qualifyObject = true)
var output = TmdlSerializer.SerializeObject(
    model.Tables["Product"].Columns["ProductKey"], qualifyObject: true);

Saída:

ref table Product

    column ProductKey
        dataType: int64
        isKey
        formatString: 0
        isAvailableInMdx: false
        lineageTag: 4184d53e-cd2d-4cbe-b8cb-04c72a750bc4
        summarizeBy: none
        sourceColumn: ProductKey

        annotation SummarizationSetBy = Automatic

3.3 Serialização em stream

Classe: MetadataSerializationContext (namespace Microsoft.AnalysisServices.Tabular.Serialization). Permite converter o objeto TOM em streams de bytes (armazenamento, transmissão, interoperabilidade) e controlar quais documentos TMDL são lidos/gravados.

// Model -> texto único
var output = new StringBuilder();
foreach (var document in model.ToTmdl())
{
    using (TextWriter writer = new StringWriter(output))
        document.WriteTo(writer);
}
// TMDL (pasta) -> Model, ignorando roles
var context = MetadataSerializationContext.Create(MetadataSerializationStyle.Tmdl);
var files = Directory.GetFiles("[TMDL Directory Path]", "*.tmdl", SearchOption.AllDirectories);

foreach (var file in files)
{
    if (file.Contains("/roles/")) continue;
    using (TextReader reader = File.OpenText(file))
        context.ReadFromDocument(file, reader);
}

var model = context.ToModel();

3.4 Tratamento de erros

ExceçãoQuando ocorre
TmdlFormatExceptionSintaxe TMDL inválida (keyword errada, indentação incorreta)
TmdlSerializationExceptionSintaxe válida, mas viola a lógica de metadados do TOM (ex.: tipo de valor incompatível)

Ambas expõem document path, line number e line text para localizar o erro.

try
{
    var model = TmdlSerializer.DeserializeDatabaseFromFolder("<TMDL Folder Path>");
}
catch (Microsoft.AnalysisServices.Tabular.Tmdl.TmdlFormatException ex)
{
    Console.WriteLine($"Erro ao desserializar TMDL '{ex.Message}', " +
        $"arquivo: '{ex.Document}', linha: '{ex.Line}', texto: '{ex.LineText}'");
    throw;
}

4. Linguagem TMDL — regras de sintaxe

4.1 Declaração de objetos

Um objeto é declarado por <tipo TOM> <nome>. Nomes devem ser envolvidos em aspas simples ('...') quando contêm: ponto (.), igual (=), dois-pontos (:), aspas simples (') ou espaço em branco. Aspas simples dentro do nome são escapadas duplicando-as ('').

model Model
    culture: en-US

table Sales
    measure Sales = SUM()
        formatString: $ #,##0

    column 'Customer Key'
        datatype: int64
        sourceColumn: CustomerKey

4.2 Propriedades do objeto

Especificadas após a declaração, com valor após o delimitador :. Regras de valores de texto:

  • Aspas duplas de abertura/fechamento são opcionais e removidas na serialização.
  • Obrigatórias se o texto tiver espaços à frente/atrás.
  • Aspas duplas internas são escapadas duplicando-as ("").
  • Booleanos: sintaxe chave: valor OU atalho — apenas o nome da propriedade implica true (ex.: isHidden sozinho).
table Sales
    lineageTag: e9374b9a-faee-4f9e-b2e7-d9aafb9d6a91

    column Quantity
        dataType: int64
        isHidden
        isAvailableInMdx: false
        sourceColumn: Quantity

    measure 'Sales Amount' =
            var result = SUMX(...)
            return result
        formatString: $ #,##0
        displayFolder: " My ""Amazing"" Measures"

4.3 Referências nomeadas (named object references)

Propriedades que referenciam outro objeto do modelo (ex.: sortByColumn, level.column, perspectiveMeasure.measure) seguem as mesmas regras de escaping/aspas simples da declaração. Nome totalmente qualificado usa notação de ponto: 'Table 1'.'Column 1'.

table Product
    column Category
        sortByColumn: 'Category Order'

    hierarchy 'Product Hierarchy'
        level Category
            column: Category

perspective Product
    perspectiveTable Product
        perspectiveMeasure '# Products'

4.4 Objetos filhos (child objects)

TMDL não declara coleções explicitamente. Todo objeto filho dentro do escopo de um pai passa implicitamente a compor a coleção correspondente no TOM. Colunas e medidas podem ser intercaladas em qualquer ordem — não precisam ser contíguas.

table Sales
    measure 'Sales Amount' = SUMX('Sales', [Quantity] * [Net Price])
    measure 'Total Quantity' = SUM('Sales'[Quantity])
    measure 'Sales Amount YTD' = TOTALYTD([Sales Amount], 'Calendar'[Date])

4.5 Propriedades padrão (default properties)

Alguns tipos de objeto (measure, partition etc.) têm uma propriedade padrão atribuída após o =, na mesma linha da declaração (single-line) ou na linha seguinte (multi-line/expression).

table Sales
    measure 'Sales Amount' = SUM(...)
        formatString: $ #,##0

    measure Quantity =
            var result = SUMX (...)
            return result
        formatString: #,##0

    partition Sales-Partition1 = m
        mode: import
        source =
            let
                ...
            in
                finalStep

4.6 Expressões (DAX / M / outros)

São lidas verbatim (texto literal), pois podem conter caracteres especiais (aspas, colchetes). Podem ser single-line ou multi-line.

Regras principais:

  • Expressões multi-line devem ser indentadas um nível a mais que as propriedades do objeto pai; todo o corpo fica dentro desse nível.
  • Espaços em branco fora da indentação são removidos; linhas em branco dentro da expressão são preservadas.
  • Espaços/linhas em branco no final são removidos.
  • Para preservar indentação ou linhas em branco no final, usar delimitador de três crases (```) logo após o =.
table Table1
    partition partition1 = m
        mode: import
        source = ```
            let
            ...
            in
                finalStep

            ```

    measure Measure1 = ```
                var myVar = Today()
                
                return result
            ```

Tabela de propriedades tratadas como expressão (linguagem entre parênteses):

Tipo de objetoPropriedadeLinguagem
MeasureExpressionDAX
Function (DAX UDF)ExpressionDAX
CalculatedColumnExpressionDAX
MPartitionSourceExpressionM
CalculatedPartitionSourceExpressionDAX
QueryPartitionSourceQueryNativeQuery
CalculationItemExpressionDAX
BasicRefreshPolicySourceExpression, PollingExpressionM
KPIStatusExpression, TargetExpression, TrendExpressionDAX
LinguisticMetadataContentXML/Json
JsonExtendedPropertyValueJson
FormatStringDefinitionExpressionDAX
DataCoverageDefinitionExpressionDAX
CalculationGroupExpressionExpressionDAX
NamedExpressionExpressionDAX/M (M para expressões de query)
DetailRowsDefinitionExpressionDAX
TablePermissionFilterExpressionDAX

4.7 Descrições

Sintaxe de comentário/documentação estilo triple-slash (///), sem espaço em branco entre o bloco de descrição e o token do tipo de objeto. Podem ser multi-linha; o serializador quebra automaticamente para manter linhas com no máximo 80 caracteres.

/// Table Description
table Sales

    /// This is the Measure Description
    /// One more line
    measure 'Sales Amount' = SUM(...)
        formatString: #,##0

4.8 Declaração parcial (partial declaration)

Um mesmo objeto pode ter sua definição dividida entre múltiplos arquivos (análogo a partial classes do C#). Ex.: definir a tabela em [table].tmdl e medidas agregadas num arquivo separado [measures].tmdl. Restrição: a mesma propriedade não pode ser declarada duas vezes — gera erro de parsing.

table Sales
    measure 'Sales Amount' = SUM()
        formatString: $ #,##0

table Product
    measure CountOfProduct = COUNTROWS()

4.9 Referências de objeto (ref)

A palavra-chave ref referencia um objeto já definido em outro arquivo (ref <tipo> <nome>), sem redeclará-lo.

Uso 1 — ordenação determinística de coleções: evita diffs desnecessários em controle de versão para objetos serializados em arquivos individuais (tabelas, roles, culturas, perspectivas). O ref no arquivo do objeto pai (ex.: model.tmdl) declara a ordem original vinda do TOM:

model Model

ref table Calendar
ref table Sales
ref table Product
ref table Customer
ref table About

ref culture en-US
ref culture pt-PT

ref role 'Stores Cluster 1'
ref role 'Stores Cluster 2'

Regras de desserialização/serialização:

  • Objeto referenciado via ref sem arquivo .tmdl correspondente → ignorado.
  • Objeto com arquivo .tmdl existente mas não referenciado → anexado ao final da coleção.
  • Na serialização, todo objeto de coleção é referenciado com ref.
  • Coleções com apenas 1 item não emitem ref.
  • Não há linha em branco entre ref's consecutivos do mesmo tipo.

4.10 Delimitadores de valor de propriedade

Apenas dois delimitadores existem em TMDL:

DelimitadorUso
= (igual)Declaração de objeto com propriedade padrão (single/multi-line); toda propriedade do tipo expressão (ex.: partition.expression)
: (dois-pontos)Qualquer valor de propriedade que não seja expressão, incluindo referências a outros objetos do modelo

4.11 Indentação

TMDL usa indentação estrita com espaço em branco (tab único como padrão) para demarcar a hierarquia do TOM. Três níveis possíveis por objeto:

  1. Nível 1 — Declaração do objeto
  2. Nível 2 — Propriedades do objeto
  3. Nível 3 — Expressões multi-linha da propriedade

A indentação é obrigatória entre: cabeçalho do objeto → suas propriedades; objeto → objetos filhos; objeto → expressões multi-linha (que devem ficar um nível mais fundo que as propriedades). Descumprir gera erro de parsing.

Database e filhos diretos de Model não precisam de indentação, pois são implicitamente aninhados sob a raiz Model/Database: model, tables, expressões compartilhadas, roles, cultures, perspectives, relationships, data sources, query groups, anotações e propriedades estendidas em nível de modelo.

4.12 Espaço em branco (whitespace)

Fora de blocos com crases (```) ou aspas duplas ("):

  • Em valores de propriedade: espaços à frente/atrás são removidos (trim).
  • Em expressões: linhas em branco no final da expressão são descartadas.
  • Linhas com apenas espaços/tabs são reduzidas a linhas vazias.

4.13 Casing (maiúsculas/minúsculas)

Na serialização/escrita, a API TMDL usa camelCase por padrão para: tipos de objeto, palavras-chave e valores de enum. Na desserialização/leitura, a API é case insensitive.


5. TMDL Scripts

Scripts TMDL aplicam uma ação (mudança/operação) a um modelo semântico. Estrutura:

<comando>
    <objeto TMDL>
    [<objeto TMDL>]
  • O comando é obrigatório e fica no topo do script.
  • Um ou mais objetos do modelo seguem, usando a linguagem/definição ou referência TMDL padrão.

5.1 Comando createOrReplace

Cria ou substitui os objetos especificados e todos os seus descendentes. Objetos existentes são substituídos pela nova definição.

  • A ordem dos objetos TMDL dentro do comando não importa.
  • Aplicam-se todas as regras semânticas da linguagem TMDL (ex.: é possível dividir a definição em múltiplos segmentos, mas a mesma propriedade não pode ser declarada mais de uma vez).

Exemplo — criar/substituir a medida # Products (with Sales) na tabela Sales (via ref) e a definição completa da tabela Product:

createOrReplace

    ref table Sales
        measure '# Products (with Sales)' = DISTINCTCOUNT('Sales'[ProductKey])
            formatString: #,##0

    table Product

        measure '# Products' = COUNTROWS('Product')
            formatString: #,##0

        column Product
            dataType: string
            isDefaultLabel
            summarizeBy: none
            sourceColumn: Product

        column Category
            dataType: string
            summarizeBy: none
            sourceColumn: Category

        partition Product-partition = m
            mode: import
            source =
                let
                    Source = #"RAW-Product",
                    #"Renamed Columns" = Table.RenameColumns(Source,{{"Product Name", "Product"}})
                in
                    #"Renamed Columns"

5.2 Considerações e limitações

  • Apenas um verbo de comando por execução de script é suportado (não é possível combinar createOrReplace com outro comando no mesmo script).

6. Onde o TMDL é usado na prática (contexto Power BI)

  • Power BI Desktop Projects (.pbip) — ao salvar um relatório como projeto, a pasta .SemanticModel é serializada em TMDL, tornando o modelo versionável (Git) arquivo a arquivo.
  • Extensão VS Code TMDL (analysis-services.TMDL) — syntax highlighting e melhor experiência de edição.
  • Automação/CI-CD — via API TmdlSerializer é possível extrair, editar programaticamente (ex.: adicionar medida) e reimplantar modelos em pipelines de deployment.
  • Scripts TMDL (createOrReplace) — equivalentes funcionais a comandos TMSL, porém com sintaxe TMDL, úteis para automação declarativa de mudanças pontuais no modelo.

7. Quick reference — cheatsheet de sintaxe

ElementoSintaxe
Declarar objeto<tipo> <nome>
Nome com espaço/caractere especial'Nome do Objeto'
Propriedade não-expressãopropriedade: valor
Propriedade booleana (atalho true)isHidden (sem : true)
Propriedade padrão / expressão<tipo> <nome> = <valor ou expressão>
Expressão multi-linhaIndentar 1 nível a mais, sem = na linha seguinte
Preservar formatação exatadelimitar com ```
Descrição do objeto/// texto na linha imediatamente acima
Referência a objeto existenteref <tipo> <nome>
Referência qualificada'Tabela'.'Coluna'
Indentação padrão1 tab por nível

8. Fontes consultadas

  1. TMDL Overview — conceitos, estrutura de pastas, linguagem completa.
  2. Get started with TMDL — API TmdlSerializer, exemplos de código C#, tratamento de erros.
  3. TMDL scripts — comando createOrReplace, sintaxe de scripts.