Migrations in Team Development
Come funziona Pragmatic.Migrations quando più sviluppatori lavorano in parallelo su branch diverse e poi fanno merge. Leggi prima il README di Pragmatic.Migrations.
Modello mentale
Section titled “Modello mentale”Pragmatic.Migrations non usa file di migration numerati come EF Core. Funziona per diff dichiarativo:
- Compile-time — il source generator scansiona le entity
[Entity]e produce uno schema desiderato (SchemaVersion), identificato da un hash. - Runtime — all’avvio il runner introspeziona lo schema reale del database, calcola il diff verso lo schema desiderato e lo applica.
Non esistono file di migration: lo “stato target” è sempre e solo ciò che le entity del codice descrivono adesso.
Cosa succede con branch concorrenti
Section titled “Cosa succede con branch concorrenti”Scenario tipico:
- Branch A aggiunge l’entity
Invoice. - Branch B aggiunge l’entity
Payment. - Entrambe partono da
maine vengono mergiate.
A differenza di EF Core — dove due file di migration creano un conflitto git esplicito sul ModelSnapshot — qui il merge git riguarda solo le classi entity. Se il merge è corretto (entrambe le classi Invoice e Payment finiscono su main), lo schema desiderato le contiene entrambe e tutto funziona: il diff produce due CreateTable, non-breaking, applicati senza intervento.
Il rischio si presenta quando il merge è risolto male: se durante la risoluzione dei conflitti una delle due classi entity viene persa (o un file non viene aggiunto), lo schema desiderato risultante non conterrà quell’entity. Git non segnala nulla — non c’è un file di migration su cui collidere.
La rete di sicurezza: breaking-change gate
Section titled “La rete di sicurezza: breaking-change gate”Questo non si traduce in una perdita dati silenziosa. Quando il runner calcola il diff tra lo schema desiderato (senza l’entity persa) e il database reale (che ha già la tabella), produce un DropTable.
DropTable e DropColumn sono marcati IsBreaking: true. Il runner (MigrationRunner) blocca l’esecuzione se il diff contiene breaking change e MigrationOptions.Force è false:
[App] Blocked: 1 breaking changes. Use Force=true. 1 breaking change(s) detected — these may cause data loss Run with DryRun=true to inspect the SQL before applying Use Force=true to apply breaking changesForce ha default false e app.UsePragmaticMigrations() non lo abilita. Quindi un merge che fa sparire un’entity ferma l’avvio dell’applicazione invece di cancellare la tabella.
Tassonomia delle change
Section titled “Tassonomia delle change”| Change | Breaking? | Note |
|---|---|---|
CreateTable | No | |
AddColumn | No | |
RenameColumn | No | Rilevata solo con [RenamedFrom] sulla property |
AlterColumnDefault | No | |
DropTable | Sì | |
DropColumn | Sì | |
AlterColumnType | Sì se narrowing | Es. varchar(256) → varchar(100) |
AlterColumnNullability | Sì se diventa NOT NULL | Dati esistenti NULL lo violerebbero |
Workflow consigliato
Section titled “Workflow consigliato”DryRunin CI — esegui la migration conMigrationOptions { DryRun = true }in pipeline su un DB allineato a produzione. Stampa la lista delle change; se compaionoDropTable/DropColumninattesi, il merge ha perso qualcosa.- Rebase frequente su
main— riduce la finestra in cui due branch divergono sullo schema. - Una entity per PR, quando possibile — minimizza i conflitti che toccano lo schema.
- Tratta il blocco breaking-change come un segnale, non un ostacolo — se all’avvio vedi
DROP TABLE Xe nessuno ha intenzionalmente rimosso l’entityX, è quasi certamente un merge incompleto: non mettereForce=true, ripristina l’entity mancante. Force=truesolo per rimozioni intenzionali — e dopo aver verificato il SQL conDryRun.- Backup prima di ogni
Force— vale come per qualsiasi DDL distruttivo.
Limiti noti
Section titled “Limiti noti”- Non c’è detection a compile-time del tipo “il codice non dichiara più un’entity che esisteva su
main”: il source generator vede solo lo stato corrente del codice. La rete di sicurezza è interamente a runtime (breaking-change gate). - Lo schema desiderato dipende dal codice mergiato, non dall’ordine di merge: due merge che producono lo stesso insieme di entity producono lo stesso schema.
- Schema tenant-specifici (colonne custom per singolo tenant) non sono supportati: lo schema desiderato è globale. Per variazioni per-tenant usare flag/colonne opzionali a livello globale.
In sintesi
Section titled “In sintesi”Il merge concorrente è sicuro finché il merge git delle classi entity è corretto. Se non lo è, il danno non è silenzioso: il breaking-change gate ferma l’applicazione e mostra esattamente quali tabelle/colonne verrebbero eliminate. La regola operativa è una sola: un DROP inatteso all’avvio = merge da rivedere, non Force da aggiungere.