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).
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 ao SQL Server Analysis Services (2016+), ao Azure Analysis Services e aos modelos semânticos do Power BI e do Fabric. No Power BI Desktop funciona com qualquer licença: é o formato dos Power BI Projects (.pbip) e da TMDL view (seção 6). Premium, PPU ou capacidade Fabric só são necessários para ler e gravar pelo endpoint XMLA, como nos exemplos da seção 3.
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.tmdlRegras 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)
// TOM Model -> pasta TMDL
public static void SerializeModelToFolder(Model model, string path)
// pasta TMDL -> TOM Database
public static Database DeserializeDatabaseFromFolder(string path)
// pasta TMDL -> TOM Model
public static Model DeserializeModelFromFolder(string path)Exemplo — extrair TMDL de um workspace (endpoint XMLA: Premium, PPU ou capacidade Fabric):
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, 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 = Automatic3.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: CustomerKey4.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
finalStep4.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: #,##04.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. TMDL view no Power BI
A TMDL view é o editor de TMDL dentro do próprio Power BI. Está
disponível para todos (GA) no Power BI Desktop e em preview no Power BI
Service. Funciona com arquivo .pbix ou .pbip, sem licença paga.
6.1 Gerar o script
- Arraste tabelas, colunas ou medidas do painel Dados para o editor. O Power
BI escreve o objeto inteiro como um script
createOrReplace. - Ou clique com o botão direito no objeto → Script TMDL (para nova aba ou para a área de transferência).
- Arrastar uma seção inteira (medidas, tabelas, colunas) gera o script de todos os objetos dela. Segure Ctrl para selecionar vários.
6.2 Editar, conferir e aplicar
| Recurso | Como usar |
|---|---|
| Autocompletar | Ao digitar, ou Ctrl + Espaço |
| Formatar | Shift + Alt + F, ou Formatar na faixa de opções |
| Erros | Sublinhados no código e listados no painel Problemas |
| Code actions | Lâmpada ao lado do erro: gera lineageTag, corrige nome de propriedade |
| Preview | Mostra o modelo antes e depois do script, como um diff |
| Aplicar | Executa o script no modelo. Detalhes de erro ficam no painel Saída |
6.3 Cuidados
- Aplicar altera só os metadados: não atualiza os dados. Se mudou uma expressão do Power Query ou de coluna calculada, atualize a tabela depois.
- Renomear um campo pode quebrar visuais do relatório que usam esse campo.
- Para renomear, gere o script do objeto pai: a tabela, para renomear uma coluna; o modelo, para renomear uma tabela.
- Se a propriedade exigir um nível de compatibilidade maior, o Power BI pede para atualizar o modelo antes de aplicar.
- As abas de script ficam salvas no arquivo. Num
.pbip, cada aba vira um.tmdlna pasta\TMDLScripts. - Editar os arquivos
.tmdlde um.pbipfora do Desktop exige reiniciar o Desktop para recarregar. A TMDL view aplica na hora.
6.4 Casos de uso
- Criar perspectivas, traduções e detail rows, que não têm tela no Desktop.
- Mudar o modo de armazenamento de uma tabela (Import ↔ DirectQuery).
- Alterar a expressão do Power Query sem disparar refresh.
- Renomear em lote com localizar e substituir por expressão regular (Ctrl + F).
- Copiar uma tabela completa (colunas, Power Query, ordenação) para outro modelo.
- Backup da definição antes de uma mudança grande.
6.5 TMDL view na web (preview)
- Tem modo Visualizar (gera e prevê scripts) e modo Editar (aplica).
- Usa o histórico de versões do workspace para voltar atrás.
- Exige permissão de escrita no modelo semântico.
- Os scripts não ficam salvos: somem ao fechar o modelo ou o navegador.
6.6 Índices de texto em colunas (preview, set/2026)
Duas propriedades novas de coluna só podem ser configuradas por código: na TMDL view do Power BI Service ou via XMLA (TOM/TMSL). Ainda não há editor gráfico.
| Propriedade | Nível de compatibilidade | Valores | Para que serve |
|---|---|---|---|
stringIndexingBehavior | 1707 ou superior | auto (padrão), full, explicit, off | Acelera buscas por trecho de texto (ex.: CONTAINSSTRING, SEARCH) |
fullTextIndexingBehavior | 1708 ou superior | full, explicit, off | Índice de texto completo exigido por TEXTCONTAINS e TEXTSIMILARITY |
table TabFatAvaliacao
column Comentario
dataType: string
sourceColumn: Comentario
fullTextIndexingBehavior: full
table TabDimProduto
column NomeProduto
dataType: string
sourceColumn: NomeProduto
stringIndexingBehavior: fullComo cada valor se comporta:
auto(só emstringIndexingBehavior): cria o índice na hora da primeira consulta que precisa dele, em memória, com limite de 25 segundos. Não ocupa espaço no modelo.full: cria o índice a cada atualização do modelo e o guarda junto com ele. É o recomendado durante o preview.explicit: só cria o índice quando você roda uma atualização do tipoindexes, por XMLA, numa transação separada depois da atualização de dados.off: não mantém índice. EmfullTextIndexingBehavior, as consultas que dependem do índice passam a dar erro.
⚠️ Subir o nível de compatibilidade não tem volta. A TMDL view na web pede a atualização sozinha quando você adiciona a propriedade.
⚠️ Vale só para colunas de texto em tabelas Import, Dual ou Direct Lake; DirectQuery puro não é suportado. Índices persistidos aumentam o tamanho do modelo e o tempo de atualização: ligue só nas colunas que são pesquisadas com frequência.
ℹ️ O índice de texto completo usa o idioma do modelo (português incluído) para radical e palavras irrelevantes. Durante o preview, o consumo de CU desses índices não é totalmente contabilizado; na GA, o custo reportado pode subir.
7. 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. - TMDL view — editor de TMDL dentro do Power BI Desktop e do Service (seção 6).
- 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.
8. 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 |
💡 Para versionar estes arquivos e voltar a uma versão anterior, veja PBIP + Git.
9. 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. - Work with TMDL view — TMDL view no Power BI Desktop e no Service.
- Configure string indexing (preview) —
stringIndexingBehavior, nível 1707. - Configure full-text indexing (preview) —
fullTextIndexingBehavior, nível 1708, TEXTCONTAINS e TEXTSIMILARITY.