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.mdcomo 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ística | Descrição |
|---|---|
| Compatibilidade total com TOM | Todo objeto TMDL expõe as mesmas propriedades do Tabular Object Model (TOM) |
| Baseado em texto | Sintaxe próxima de YAML, com indentação demarcando hierarquia pai-filho, poucos delimitadores |
| Melhor edição | Melhor experiência para propriedades com expressões embutidas (DAX, M) |
| Colaboração | Representaçã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 paramodel. - 1 arquivo único agregando todas as
dataSources, todas asexpressions, todas asfunctions(DAX User Defined Functions) e todos osrelationshipsdo 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.tmdlem VS Code (com a extensão TMDL) →DeserializeModelFromFolder+CopyTo+SaveChangespara 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ção | Quando ocorre |
|---|---|
TmdlFormatException | Sintaxe TMDL inválida (keyword errada, indentação incorreta) |
TmdlSerializationException | Sintaxe 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: valorOU atalho — apenas o nome da propriedade implicatrue(ex.:isHiddensozinho).
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 objeto | Propriedade | Linguagem |
|---|---|---|
| Measure | Expression | DAX |
| Function (DAX UDF) | Expression | DAX |
| CalculatedColumn | Expression | DAX |
| MPartitionSource | Expression | M |
| CalculatedPartitionSource | Expression | DAX |
| QueryPartitionSource | Query | NativeQuery |
| CalculationItem | Expression | DAX |
| BasicRefreshPolicy | SourceExpression, PollingExpression | M |
| KPI | StatusExpression, TargetExpression, TrendExpression | DAX |
| LinguisticMetadata | Content | XML/Json |
| JsonExtendedProperty | Value | Json |
| FormatStringDefinition | Expression | DAX |
| DataCoverageDefinition | Expression | DAX |
| CalculationGroupExpression | Expression | DAX |
| NamedExpression | Expression | DAX/M (M para expressões de query) |
| DetailRowsDefinition | Expression | DAX |
| TablePermission | FilterExpression | DAX |
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
refsem arquivo.tmdlcorrespondente → ignorado. - Objeto com arquivo
.tmdlexistente 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:
| Delimitador | Uso |
|---|---|
= (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:
- Nível 1 — Declaração do objeto
- Nível 2 — Propriedades do objeto
- 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
createOrReplacecom 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
| Elemento | Sintaxe |
|---|---|
| Declarar objeto | <tipo> <nome> |
| Nome com espaço/caractere especial | 'Nome do Objeto' |
| Propriedade não-expressão | propriedade: valor |
Propriedade booleana (atalho true) | isHidden (sem : true) |
| Propriedade padrão / expressão | <tipo> <nome> = <valor ou expressão> |
| Expressão multi-linha | Indentar 1 nível a mais, sem = na linha seguinte |
| Preservar formatação exata | delimitar com ``` |
| Descrição do objeto | /// texto na linha imediatamente acima |
| Referência a objeto existente | ref <tipo> <nome> |
| Referência qualificada | 'Tabela'.'Coluna' |
| Indentação padrão | 1 tab por nível |
8. Fontes consultadas
- TMDL Overview — conceitos, estrutura de pastas, linguagem completa.
- Get started with TMDL — API
TmdlSerializer, exemplos de código C#, tratamento de erros. - TMDL scripts — comando
createOrReplace, sintaxe de scripts.