flowchart LR
subgraph REPO["Repository"]
direction TB
GH[".github/<br/>copilot-instructions.md · instructions/ · prompts/ · CODEOWNERS · workflows/"] ~~~ VS[".vscode/mcp.json<br/>MCP servers for agent mode"]
VS ~~~ DB["database/<br/>Contoso.Database.sqlproj · object scripts · publish profiles"]
DB ~~~ API["api/<br/>dab-config.json"]
end Versions these samples target
Data API builder 2.1, Microsoft.Build.Sql 2.3.0, SqlPackage 170.5, actions/checkout@v7,
actions/setup-dotnet@v6, actions/upload-artifact@v7,
actions/download-artifact@v8, azure/login@v3 and azure/sql-action@v2 — the
current majors in November 2026. Older Learn samples pin earlier majors; the syntax that matters for the exam is
the same.
dab-config.json
One file configures the data source, the three endpoints, security, caching, paging and every entity. Comments are shown for annotation — remove them in a real file, or keep the file valid JSON.
{
"$schema": "https://github.com/Azure/data-api-builder/releases/download/v2.1.5/dab.draft.schema.json",
"data-source": {
"database-type": "mssql",
// Never a literal: @env() reads an environment variable, @akv() a Key Vault secret
"connection-string": "@env('SQL_CONNECTION_STRING')"
},
"runtime": {
"rest": { "enabled": true, "path": "/api" },
"graphql": { "enabled": true, "path": "/graphql", "allow-introspection": false },
"mcp": {
"enabled": true,
"path": "/mcp",
"description": "Product catalog for the Contoso storefront", // sent to MCP clients as instructions
"dml-tools": {
"describe-entities": true,
"read-records": true,
"create-record": false,
"update-record": false,
"delete-record": false, // a common production choice
"execute-entity": true,
"aggregate-records": { "enabled": true, "query-timeout": 30 }
}
},
"host": {
"mode": "production", // development enables Simulator and richer errors
"cors": { "origins": ["https://shop.contoso.com"], "allow-credentials": false },
"authentication": {
"provider": "EntraId", // formerly AzureAd; default is Unauthenticated
"jwt": {
"audience": "api://contoso-catalog",
"issuer": "https://login.microsoftonline.com/<tenant-id>/v2.0"
}
}
},
"cache": { "enabled": true, "ttl-seconds": 30 },
"pagination": { "default-page-size": 100, "max-page-size": 1000 },
"telemetry": { "application-insights": { "enabled": true, "connection-string": "@env('APPLICATIONINSIGHTS_CONNECTION_STRING')" } }
},
"entities": {
"Product": {
"source": { "object": "dbo.Product", "type": "table", "object-description": "Products for sale" },
"fields": [
{ "name": "ProductID", "primary-key": true },
{ "name": "ListPrice", "alias": "price", "description": "Price in EUR" } // DAB 2.x: replaces "mappings"
],
"rest": { "enabled": true, "path": "/products" },
"graphql": { "enabled": true, "type": { "singular": "Product", "plural": "Products" } },
"cache": { "enabled": true, "ttl-seconds": 60, "level": "L1" },
"permissions": [
{ "role": "anonymous", "actions": [ { "action": "read", "fields": { "exclude": ["Cost", "Embedding"] } } ] },
{ "role": "catalogEditor", "actions": [ "create", "read", "update" ] }
],
"relationships": {
"category": {
"cardinality": "one",
"target.entity": "Category",
"source.fields": ["CategoryID"],
"target.fields": ["CategoryID"]
}
}
},
"Category": {
"source": { "object": "dbo.Category", "type": "table" },
"permissions": [ { "role": "anonymous", "actions": [ "read" ] } ],
"relationships": {
"products": { "cardinality": "many", "target.entity": "Product" } // inferred from the foreign key
}
},
"MyOrders": {
"source": { "object": "dbo.vw_OrderSummary", "type": "view" },
"fields": [ { "name": "OrderNo", "primary-key": true } ], // views need a key (was "key-fields")
"permissions": [
{
"role": "authenticated",
"actions": [ { "action": "read",
"policy": { "database": "@item.CustomerOid eq @claims.oid" } } ] // per-user rows
}
]
},
"PlaceOrder": {
"source": {
"object": "dbo.usp_PlaceOrder",
"type": "stored-procedure",
"parameters": [ { "name": "CustomerID", "required": true, "description": "Buyer" } ] // DAB 2.x array format
},
"rest": { "methods": ["post"] },
"graphql": { "operation": "mutation" },
"mcp": { "custom-tool": true }, // exposed as a named MCP tool
"permissions": [ { "role": "authenticated", "actions": [ "execute" ] } ]
}
}
} | Section | What the exam asks about it |
|---|---|
data-source.connection-string | Comes from @env() or @akv() (Key Vault, with a root azure-key-vault.endpoint), ideally with Authentication=Active Directory Managed Identity — never a literal secret |
| Column types | DAB doesn't serialize json, vector, xml, spatial, hierarchyid or sql_variant columns — expose them through a view or procedure that casts or omits them |
runtime.host.authentication | EntraId/Custom validate JWTs; AppService trusts EasyAuth; Unauthenticated (default) is anonymous-only; Simulator needs development mode |
runtime.host.mode | production for deployed APIs |
runtime.mcp.dml-tools | Global on/off per tool; a disabled tool is hidden from list_tools whatever the entity permissions |
entities.*.permissions | Role → actions, field include/exclude, database policy with @item and @claims |
entities.*.fields | DAB 2.x replacement for mappings (alias) and key-fields (primary-key) — the two can't be mixed |
entities.*.relationships | cardinality one/many, target.entity, optional field lists, linking.object for many-to-many (GraphQL) |
entities.*.cache | enabled, ttl-seconds (default 5, or the runtime value) and level L1 or L1L2 (Redis-backed) |
DAB CLI
# Create the file (the connection string stays in an environment variable)
dab init --database-type mssql --connection-string "@env('SQL_CONNECTION_STRING')" --host-mode development
# Add entities: a table, a view with a key, a stored procedure
dab add Product --source dbo.Product --permissions "anonymous:read"
dab add MyOrders --source dbo.vw_OrderSummary --source.type view \
--fields.name OrderNo --fields.primary-key true --permissions "authenticated:read"
dab add PlaceOrder --source dbo.usp_PlaceOrder --source.type stored-procedure \
--permissions "authenticated:execute" --rest.methods "post" --graphql.operation "mutation"
# Evolve them
dab update Product --relationship category --target.entity Category --cardinality one
dab update Product --cache.enabled true --cache.ttl-seconds 60
dab configure --runtime.host.authentication.provider EntraId
dab configure --runtime.mcp.enabled true
# Check and run
dab validate # schema, permissions and database connectivity
dab start # REST /api, GraphQL /graphql, MCP /mcp on http://localhost:5000
dab start --mcp-stdio role:anonymous --config ./dab-config.json # MCP over stdio for a local client SDK-style .sqlproj
<Project DefaultTargets="Build">
<Sdk Name="Microsoft.Build.Sql" Version="2.3.0" />
<PropertyGroup>
<Name>Contoso.Database</Name>
<!-- Target platform: Sql170 = SQL Server 2025, SqlAzureV12 = Azure SQL Database,
SqlDbFabric = SQL database in Fabric (preview) -->
<DSP>Microsoft.Data.Tools.Schema.Sql.SqlAzureV12DatabaseSchemaProvider</DSP>
<ModelCollation>1033, CI</ModelCollation>
<RunSqlCodeAnalysis>True</RunSqlCodeAnalysis>
<TreatTSqlWarningsAsErrors>False</TreatTSqlWarningsAsErrors>
</PropertyGroup>
<!-- Every .sql file under the folder is part of the model by default (globbing).
Scripts that are not schema objects are taken out of the build and wired as deployment scripts. -->
<ItemGroup>
<Build Remove="Scripts\**\*.sql" />
<PreDeploy Include="Scripts\PreDeployment.sql" />
<PostDeploy Include="Scripts\PostDeployment.sql" />
</ItemGroup>
<!-- Per-environment values, referenced in scripts as $(Environment) -->
<ItemGroup>
<SqlCmdVariable Include="Environment">
<DefaultValue>dev</DefaultValue>
</SqlCmdVariable>
</ItemGroup>
<!-- Database references as NuGet packages, for example the master database -->
<ItemGroup>
<PackageReference Include="Microsoft.SqlServer.Dacpacs.Azure.Master" Version="170.0.4" />
</ItemGroup>
</Project> - Globbing: every
.sqlfile is in the model unless removed — that is why deployment scripts are taken out withBuild Removeand wired asPreDeploy/PostDeploy. - One pre- and one post-deployment script per project; include further files from them with SQLCMD
:r. - SQLCMD variables carry per-environment values; set them in a publish profile or with
/v:Name=value. - Build with
dotnet buildto getbin/<config>/Contoso.Database.dacpac.
Publish profile
<?xml version="1.0" encoding="utf-8"?>
<!-- publish/Production.publish.xml — deployment options, never credentials -->
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<TargetDatabaseName>ContosoDb</TargetDatabaseName>
<BlockOnPossibleDataLoss>True</BlockOnPossibleDataLoss>
<DropObjectsNotInSource>False</DropObjectsNotInSource>
<BlockWhenDriftDetected>True</BlockWhenDriftDetected>
<RegisterDataTierApplication>True</RegisterDataTierApplication>
<IncludeTransactionalScripts>True</IncludeTransactionalScripts>
</PropertyGroup>
<ItemGroup>
<SqlCmdVariable Include="Environment">
<Value>prod</Value>
</SqlCmdVariable>
</ItemGroup>
</Project> BlockOnPossibleDataLoss(default true)- Stops a deployment that would drop a column, narrow a type or otherwise lose data.
DropObjectsNotInSource(default false)- Deletes objects in the target that are not in the project — powerful, dangerous on shared databases.
RegisterDataTierApplicationandBlockWhenDriftDetected- Register the deployed schema so later deployments (and
DriftReport) can detect out-of-band changes.
GitHub Actions deploy workflow
name: Database CI/CD
on:
pull_request:
branches: [main]
paths: ['database/**']
push:
branches: [main]
paths: ['database/**']
workflow_dispatch:
permissions:
contents: read
jobs:
build-and-test:
runs-on: ubuntu-latest
services:
sql: # throwaway SQL Server for integration tests
image: mcr.microsoft.com/mssql/server:2025-latest
env:
ACCEPT_EULA: 'Y'
MSSQL_SA_PASSWORD: ${{ secrets.CI_SA_PASSWORD }}
ports: ['1433:1433']
steps:
- uses: actions/checkout@v7
- uses: actions/setup-dotnet@v6
with:
dotnet-version: '10.0.x'
- name: Build the model (validates every reference)
run: dotnet build database/Contoso.Database.sqlproj -c Release
- name: Deploy to the CI container and run tests
run: |
dotnet tool install -g microsoft.sqlpackage
sqlpackage /Action:Publish \
/SourceFile:database/bin/Release/Contoso.Database.dacpac \
/TargetConnectionString:"Server=localhost;Database=CiDb;User ID=sa;Password=${{ secrets.CI_SA_PASSWORD }};TrustServerCertificate=True"
# run unit and integration tests here (for example tSQLt via sqlcmd)
- uses: actions/upload-artifact@v7
with:
name: dacpac
path: database/bin/Release/Contoso.Database.dacpac
deploy-production:
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
needs: build-and-test
runs-on: ubuntu-latest
environment: production # required reviewers, branch rule, env secrets
permissions:
contents: read
id-token: write # OIDC token for azure/login: no client secret
steps:
- uses: actions/checkout@v7
- uses: actions/download-artifact@v8
with:
name: dacpac
- uses: azure/login@v3
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
- name: Review the plan
uses: azure/sql-action@v2
with:
connection-string: ${{ secrets.PROD_SQL_CONNECTION_STRING }} # Authentication=Active Directory Default
path: ./Contoso.Database.dacpac
action: DeployReport
- name: Publish
uses: azure/sql-action@v2
with:
connection-string: ${{ secrets.PROD_SQL_CONNECTION_STRING }}
path: ./Contoso.Database.dacpac
action: Publish
arguments: '/p:BlockOnPossibleDataLoss=true' What each control in the workflow does
pull_request+paths: validate only database changes; the PR can't merge until this check passes (a required status check in the branch ruleset).environment: production: the job waits for the environment's required reviewers and can only read that environment's secrets.id-token: write+azure/loginwithclient-id,tenant-id,subscription-id: OIDC federation — no client secret anywhere.- One
.dacpacartifact, built once and deployed to every environment. azure/sql-actionactions:Publish,Script,DeployReport,DriftReport; it builds a.sqlprojitself when given one.
CODEOWNERS
# .github/CODEOWNERS — the last matching pattern wins
* @contoso/app-team
/database/ @contoso/dba-team
/database/Security/ @contoso/dba-team @contoso/security
/.github/workflows/ @contoso/platform-team CODEOWNERS only requests reviews; it becomes a control when the branch ruleset requires "review from Code Owners" before merging.
GitHub Copilot instruction files
# Contoso database — instructions for GitHub Copilot
## Platform
- Azure SQL Database, compatibility level 170. Do not use features that need PREVIEW_FEATURES.
## T-SQL conventions
- Schema-qualify every object (dbo.Customer). List columns explicitly; never SELECT *.
- New procedures: CREATE OR ALTER, SET NOCOUNT ON, SET XACT_ABORT ON, TRY...CATCH with THROW.
- Store semi-structured data in json columns; add a JSON index for queried paths.
- Embeddings: vector(1536) from the external model Embed3Small; search with VECTOR_SEARCH
and SELECT TOP (N) WITH APPROXIMATE, never the deprecated TOP_N parameter.
## Security
- Parameterize all input; dynamic SQL only through sp_executesql with parameters.
- Never generate GRANT ... TO public, logins with passwords, credentials or connection strings.
## Project
- Schema lives in database/ as an SDK-style SQL project; one object per file under Schema/ObjectType/.
- Reference data goes in Scripts/PostDeployment.sql as an idempotent MERGE. ---
applyTo: "database/**/*.sql"
excludeAgent: "code-review"
---
# Rules for database object scripts
- File name equals the object name: database/dbo/Tables/Customer.sql.
- Tables need a named primary key constraint (PK_<Table>) and named defaults (DF_<Table>_<Column>).
- Foreign key columns get a nonclustered index. ---
description: Generate a stored procedure that follows the team template
agent: agent
tools: ['mssql/*']
---
Create a stored procedure named ${input:procName:usp_VerbNoun} that ${input:purpose}.
Follow .github/copilot-instructions.md. Include:
- SET NOCOUNT ON and SET XACT_ABORT ON
- an explicit transaction for any data modification, inside TRY...CATCH with THROW
- a header comment describing parameters and result sets
Write it to database/dbo/StoredProcedures/${input:procName}.sql. | File | Frontmatter | Applied |
|---|---|---|
copilot-instructions.md | None | Automatically, to every request in the repository |
*.instructions.md | applyTo (comma-separated globs), optional excludeAgent (code-review or cloud-agent) | Automatically, when a matching file is in context — together with the repository-wide file |
*.prompt.md | description, name, agent, model, tools, argument-hint | On demand, as /name in chat; inputs with ${input:name:placeholder} |
AGENTS.md | None | For agents; the nearest file up the tree takes precedence |
Precedence when several sets apply: personal instructions first, then repository, then organization — all are sent, so avoid conflicts rather than relying on order. Background in Skill 1.
MCP server configuration
{
"inputs": [
{ "id": "catalog_token", "type": "promptString", "description": "Token for the catalog API", "password": true }
],
"servers": {
// SQL MCP Server started by VS Code as a child process (local development)
"contoso-sql-local": {
"type": "stdio",
"command": "dab",
"args": ["start", "--mcp-stdio", "role:anonymous", "--config", "${workspaceFolder}/api/dab-config.json", "--LogLevel", "error"]
},
// SQL MCP Server already running in Azure Container Apps (HTTP transport)
"contoso-sql-remote": {
"type": "http",
"url": "https://contoso-catalog.azurecontainerapps.io/mcp",
"headers": { "Authorization": "Bearer ${input:catalog_token}" } // prompted, never stored
},
// A Fabric data agent published as an MCP server (lakehouse or warehouse data)
"fabric-sales-agent": {
"type": "http",
"url": "<MCP server URL copied from the data agent's Settings → Model Context Protocol tab>"
}
}
} stdio: the client launches the server process (no port, no network); DAB forces Simulator authentication and runs every call as the role after--mcp-stdio.http: connect to a running server's/mcpURL; authenticate with headers fed frominputs(prompted, optionally masked) or an Entra sign-in.- Start, stop and inspect servers with MCP: List Servers; enable or disable individual tools in agent mode's tools picker.
- Visual Studio reads
.mcp.jsonand.vscode/mcp.json; GitHub Copilot in SSMS 22.7+ supports MCP servers in agent mode.