This file is the entry-point operating manual for coding agents working on Dax.Template.
It is self-sufficient: it does not assume you have read README.md.
It stays lean on purpose — go to the linked docs/design/ docs for depth.
Dax.Template is a .NET library, published as the Dax.Template NuGet package, that generates Tabular Object Model (TOM) objects — DAX columns, measures, calculated tables, hierarchies — into an Analysis Services / Power BI Tabular model, driven by JSON template configuration files.
Consumers load a template package, then call into the engine to mutate an in-memory or connected TOM Model.
- Restore:
dotnet restore ./src - Build:
dotnet build ./src/Dax.Template.sln --configuration Release - Test (all offline):
dotnet test ./src/Dax.Template.Tests/Dax.Template.Tests.csproj --configuration Release - Single test:
dotnet test ./src/Dax.Template.Tests/Dax.Template.Tests.csproj --filter "FullyQualifiedName~<TestName>" - Offline-only (exclude opt-in live-server tests): add
--filter "FullyQualifiedName!~LiveServer"(usually unnecessary — see below) - Pack:
dotnet pack ./src/Dax.Template/Dax.Template.csproj --configuration Release - Single project build:
dotnet build ./src/Dax.Template/Dax.Template.csproj --configuration Release
- SDK is pinned via global.json to
10.0.301(rollForward: latestFeature); install/use that .NET SDK. - All three projects target
net10.0(Dax.Template.TestUItargetsnet10.0-windows),LangVersion 14.0. - No external services are needed to build or run the offline test suite — the tests build a synthetic, disconnected TOM
Databasein memory. src/Dax.Template.TestUIis a WinForms manual harness (net10.0-windows) for interactively exercising templates against a real or offline model; it is not part of automated CI.- Live-server tests are opt-in only: set
DAXTEMPLATE_LIVE_SERVERandDAXTEMPLATE_LIVE_DATABASEenv vars to un-skip them (see testing.md).
- Entry point:
Engine.ApplyTemplates(src/Dax.Template/Engine.cs) readsConfiguration.Templates[]and dispatches each entry by itsClassstring to a handler (HolidaysDefinitionTable,HolidaysTable,CustomDateTable,MeasuresTemplate,CalendarTemplate,CalculationGroupTemplate,FunctionLibraryTemplate). - Each handler finds-or-creates the target TOM
Table, builds a template object (aTables/*orMeasures/*class), calls itsApplyTemplate(...), and requests a table refresh (skipped automatically for disconnected/offline models).CalendarTemplateis the exception: it requires an existing table (TemplateExceptionif not found) and attaches a native TOMCalendarrather than generating table content, so no refresh is requested.CalculationGroupTemplategenerates its own table (build-then-add, so an invalid definition never leaves a phantom table) but also skips the refresh — calculation items are pure metadata, and theCalculationGroupSourcepartition has no query.FunctionLibraryTemplateis model-level, not table-level: it never finds/creates aTable(TemplateEntry.Tableis unused) and there is nothing to refresh. Engine.GetModelChangescomputes a diff of what changed in the model (added/removed/modified tables, columns, measures, hierarchies) by reading TOM's internal transaction log via reflection.- Table generation flows through
Tables/TableTemplateBase→Tables/CalculatedTableTemplateBase→Tables/ReferenceCalculatedTable→Tables/CustomTableTemplate<T>→Tables/Dates/BaseDateTemplate<T>, withCustomDateTable,SimpleDateTable,HolidaysTableas concrete date-table templates;HolidaysDefinitionTablesits directly onCalculatedTableTemplateBase. Tables/Calendars/CalendarTemplate(+CalendarTemplateDefinition) sits outside that hierarchy: it attaches a TOMCalendarand itsCalendarColumnGroupsto a table a prior template already created, keyed byCalendar.Namefor idempotency (aCalendarhas noAnnotations, so it can't use theSQLBI_Templateconvention below). Requires database compatibility level >= 1701.Tables/CalculationGroups/CalculationGroupTemplate(+CalculationGroupTemplateDefinition) also sits outside that hierarchy: it generates a calculation-group table — a native TOMCalculationGroupand itsCalculationItems — from JSON, independent of theMeasures/Syntaxmachinery. Keyed for idempotency by the generated table'sSQLBI_Template = "CalculationGroup"annotation, with a foreign-table guard that refuses to take over a same-named table lacking that annotation. Requires database compatibility level >= 1470 (calculation groups), >= 1500 (calculation items), and >= 1605 (the two selection expressions).Functions/FunctionLibraryTemplate(+FunctionLibraryTemplateDefinition) is the only model-level template: it attaches DAX user-defined functions (UDFs) directly toModel.Functions, not to anyTable. One sub-template file is a library declaring one or more functions (structuredParameters/Bodyor aRawExpressionescape hatch). Keyed for idempotency by each generatedFunction's ownSQLBI_Template = "Functions"annotation (Attributes.SqlbiTemplateFunctions) — reconciled by name, so unlikeCalendarTemplatea renamed function is not orphaned. Requires database compatibility level >= 1702. See docs/design/functions.md.- Measures are generated by
Measures/MeasuresTemplate+Measures/MeasureTemplateBase(time-intelligence-style wrapping of target measures), tagged with aSQLBI_Templateannotation so re-applying a template replaces its own prior output and cleans up orphans. - See docs/design/ for the full picture, including a lifecycle diagram.
src/Dax.Template/— the library (IsPackable=true; the shipped NuGet package). Notable top-level folders:Tables/(table generation, includingTables/Calendars/andTables/CalculationGroups/),Measures/, andFunctions/(model-level DAX user-defined functions — seeFunctionLibraryTemplateabove).src/Dax.Template.Tests/— xUnit offline test suite (golden-file snapshots).src/Dax.Template.TestUI/— WinForms manual harness, not automated.- Dependency direction is one-way:
Dax.Template.TestsandDax.Template.TestUIreferenceDax.Template;Dax.Templatehas no dependency on either.
- JSON template config changes must be purely additive — existing template JSON files must keep working unchanged.
- Model objects (
Model/Column,Model/Hierarchy,Model/Level) hold an internalTabular*back-reference (e.g.Column.TabularColumn) to the live TOM object they created;Reset()nulls these before a template is re-applied, so templates are safely re-runnable. - Generated measures/tables carry a
SQLBI_Templateannotation (Constants/Attributes.cs) used for idempotency: re-running a template replaces its own prior output and removes orphaned objects it previously created. [InternalsVisibleTo("Dax.Template.Tests")]insrc/Dax.Template/AssemblyInfo.cslets tests observe internalTabular*members directly.- Testing is an offline golden-file (snapshot) harness: CI gates only on these tests. Live-server tests exist but are opt-in and never required for sign-off (see testing.md).
- Multi-phase roadmap (Calendars → Calc groups → UDFs) is tracked in .claude/SESSION_HANDOFF.md — read it before resuming that work.
- A
cwm-roslyn-navigatorMCP server (fromdotnet-claude-kit) is available for precise .NET semantic navigation — call graphs, overrides, type hierarchy, diagnostics, anti-patterns, dead/circular code — complementary to Serena (seeCLAUDE.mdfor the full agent/tooling policy). - New C# should target the modern C# 14 / .NET 10 baseline (kit
modern-csharpskill). The auto-format-on-edit hook is disabled for this repo; rundotnet formatexplicitly when needed.
- Analyzers are enabled repo-wide via
src/Directory.Build.props(EnableNETAnalyzers,AnalysisLevel=latest-recommended,EnforceCodeStyleInBuild=true,Nullable=enable). dotnet formatis the formatting authority — run it explicitly (the auto-format-on-edit hook is disabled); CI verifies the baseline withdotnet format --verify-no-changesand fails on drift.- Warnings-as-errors is a CI-only gate: CI passes
-p:TreatWarningsAsErrors=trueto the build command (local dev builds stay lenient). TheWarningsNotAsErrorsallowlist insrc/Directory.Build.props, which used to cover the Stage-2 analyzer-debt codes, is now empty (Stage 3, 2026-07-03 — the last two codes, CA1305/CA1309, were fixed and removed): any analyzer or compiler warning (including compilerCSxxxxwarnings) now fails CI. Do not add a code back to silence a newly-introduced warning — fix it instead. CA1707(identifiers should not contain underscores) is disabled forsrc/Dax.Template.Tests/**only, via.editorconfig, because the idiomatic xUnitMethod_Scenario_Expectedtest naming convention relies on underscores. The rule stays active forsrc/Dax.Templateproduction code and is fully clean there — the Stage 2 public-API rename sweep (2.10b) eliminated all 18 production hits andCA1707was removed from theWarningsNotAsErrorsallowlist.- File-scoped namespaces (
csharp_style_namespace_declarations = file_scoped:suggestion) are the documented house style going forward; the existing block-scoped code is intentionally left as-is until a dedicated Stage 2 mechanical sweep converts it.
- docs/design/README.md — index of all design docs; start here for anything not covered above.
- docs/design/overview.md — read for system context and package purpose before making cross-cutting changes.
- docs/design/apply-templates-lifecycle.md — read before touching
Engine.csor adding a new templateClass. - docs/design/table-generation.md — read before touching table/column/hierarchy/date-table generation code under
Tables/. - docs/design/measures.md — read before touching
Measures/or theSQLBI_Templateidempotency logic. - docs/design/functions.md — read before touching
Functions/or the DAX user-defined-function (UDF) JSON schema. - docs/design/domain-model-and-conventions.md — read before touching
Model/, theSyntax/DAX expression subsystem, or dependency ordering. - docs/design/testing.md — read before adding/changing tests or the golden-file harness.
- docs/design/coverage.md — read before changing the coverlet configuration, the CI coverage gate,
[ExcludeFromCodeCoverage]usage, or the Stryker.NET mutation-testing setup.
Documentation is living.
Whenever a change affects behavior, architecture, public API/contracts, project layout, or conventions, update the affected docs — this file, its Documentation map, and the relevant docs/design/* file — as part of the same change.
If a doc cannot be updated right away, add a short status banner at its top marking it stale/historical rather than leaving it silently wrong.