SQL Database Projects: la single source of truth
Un SQL Database Project (.sqlproj, oggi in formato SDK-style basato su Microsoft.Build.Sql) descrive lo schema in modo dichiarativo: ogni table, view, stored procedure o function vive come file .sql versionato in Git. La build produce un DACPAC, ovvero lo stato desiderato dello schema. Il progetto — non il database di produzione — diventa la single source of truth.
Mettere il progetto sotto source control significa che ogni modifica di schema passa da un commit e, idealmente, da una pull request con review. Anche gli oggetti che abilitano scenari AI (colonne di tipo vector, stored procedure che invocano endpoint REST esterni, view che espongono embedding) sono schema a tutti gli effetti e vanno versionati nello stesso progetto; verifica però che la versione del tooling supporti i tipi più recenti prima di affidartici in pipeline.
State-based vs migration-based
Il deploy con SQL Database Projects è state-based (dichiarativo): SqlPackage confronta il DACPAC (stato desiderato) con il database target (stato attuale) e genera automaticamente lo script incrementale di differenza. È intrinsecamente idempotente — rilanciarlo su un database già allineato non produce modifiche — il che lo rende ideale per rilasci ripetibili.
L’alternativa è l’approccio migration-based (script ordinati, es. DbUp o EF migrations), dove scrivi tu ogni trasformazione. Regola pratica: scegli state-based quando vuoi un’unica fonte dichiarativa e una drift detection semplice; migration-based quando servono data motion complessi o controllo fine sull’ordine delle operazioni. Con lo state-based, le trasformazioni di dati non banali si gestiscono con pre/post-deployment script inclusi nel progetto.
Le opzioni di pubblicazione (spesso raccolte in un publish profile .publish.xml) governano la sicurezza del rilascio: BlockOnPossibleDataLoss (attivo di default) blocca deploy che eliminerebbero colonne o tabelle con perdita dati; DropObjectsNotInSource forza il target ad aderire esattamente al progetto rimuovendo ciò che non è nel source — potente ma pericoloso.
Pipeline CI/CD con GitHub Actions
Una pipeline tipica separa CI e CD. In CI, su ogni push o PR, dotnet build compila il .sqlproj in un DACPAC: se lo schema non compila, il rilascio si ferma prima di toccare qualsiasi database. Il DACPAC diventa l’artifact della build. In CD, il DACPAC viene pubblicato sull’ambiente target, in genere con l’action ufficiale azure/sql-action (che sa deployare DACPAC, script o progetti) oppure invocando direttamente SqlPackage.
Per l’autenticazione preferisci azure/login con service principal o managed identity via OIDC, anziché una connection string con password nei secret. Usa i GitHub Environments per separare dev/test/prod e applicare protection rules con approvazione manuale prima del deploy in produzione. Un flusso maturo esegue prima un deploy report (diff senza applicare) come gate di review, poi il deploy effettivo.
Gestire lo schema drift
Lo schema drift è la divergenza tra il database reale e ciò che è definito nel progetto: nasce quando qualcuno applica una hotfix manuale direttamente in produzione. Il rischio è duplice: un deploy state-based potrebbe sovrascrivere la modifica manuale, oppure fallire per conflitti inattesi.
Si rileva per confronto: SqlPackage può generare un deploy report o uno script di diff, e Schema Compare mostra le differenze oggetto per oggetto. La disciplina corretta è che ogni cambiamento passi dal progetto e dalla pipeline; le modifiche manuali vanno riportate (back-port) nel source control appena scoperte. Un deploy report schedulato contro produzione è un ottimo early-warning sul drift.
Trappole tipiche d’esame
- Deploy che elimina una colonna fallisce senza motivo apparente → soluzione: è
BlockOnPossibleDataLoss(default attivo) che protegge dai data loss; non disabilitarlo alla cieca, gestisci la migrazione dei dati con un pre-deployment script. - Un oggetto creato a mano in produzione non viene rimosso dal deploy → soluzione: il deploy normale fa solo add/alter; per far aderire il target esattamente al progetto serve
DropObjectsNotInSource, da usare con cautela. - Timore di rieseguire la pipeline due volte → soluzione: il deploy state-based è idempotente; su un database già allineato non genera modifiche, quindi è sicuro per rilasci ripetibili.
- Credenziali del database esposte come secret → soluzione: preferisci
azure/logincon service principal/managed identity via OIDC alla connection string con password. - Deploy in produzione senza controllo umano → soluzione: usa i GitHub Environments con protection rules e approvazione manuale, non un semplice trigger su push a main.
- Colonna vector o oggetto AI recente che rompe la build del DACPAC → soluzione: verifica che la versione di DacFx/SqlPackage supporti il tipo prima di includerlo in pipeline; nel dubbio consulta la documentazione ufficiale.