Back to "Probabilmente stai sbagliando le Migrazioni EF..."

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

CI Entity Framework GitHub Migrations

Probabilmente stai sbagliando le Migrazioni EF...

Sunday, 23 November 2025

In esecuzione MigrateAsync() All'avvio? Stai dando i diritti del proprietario del database delle app e sperando che nulla vada storto. C'è un modo migliore - I pacchetti di migrazione EF ti permettono di eseguire le migrazioni come un passo CI controllato, mantenendo la tua app di produzione sicura. Ma ecco la cosa: a volte il modo "sbagliato" è in realtà bene. Esploriamo quando usare ogni approccio.

Documenti ufficiali: Panoramica delle migrazioni | Applicazione delle migrazioni | BundlesCity name (optional, probably does not need a translation)

Il modo "sbagliato" (che uso)

Questo blog utilizza MigrateAsync() all'avvio - l'approccio che sto per dirti di non usare. Ecco perché va bene per me, e perché probabilmente non è per te.

Nella mia Program.cs file Ho il seguente:

    using (var scope = app.Services.CreateScope())
    {
        var blogContext = scope.ServiceProvider.GetRequiredService<IMostlylucidDBContext>();
        await blogContext.Database.MigrateAsync();
    }

MigrateAsync() applica le migrazioni pendenti e crea la banca dati se necessario. Semplice - ma problematico:

  1. Dipendenza dall'avvio La migrazione fallisce, la tua app non parte.
  2. Violazione della sicurezza - La tua app ha bisogno di db_owner Hai appena dato alla tua app runtime le chiavi per lasciare i tavoli.

Perché la faccio franca: dati pubblici, rete Docker singola, progetto personale. Probabilmente non puoi.

Quando le migrazioni di runtime vanno bene

  • Dev localeCity name (optional, probably does not need a translation) - Iterazione veloce batte la cerimonia
  • Progetti personali - Basso raggio di esplosione, nessun dato sensibile
  • Docker-componi ambienti dev - Vince la comodità
  • Prototipazione - Lo schema sta cambiando costantemente comunque

Quando non lo sono

  • Casi multipli di app - Condizioni di gara galore
  • Dati sensibili - PII, finanziario, regolamentato = adeguata separazione richiesta
  • Produzione con utilizzatori reali - Migrazione non riuscita = interruzione

Il modo giusto: EF Bundles

Un pacchetto EF è un eseguibile autonomo contenente le migrazioni compilate. dotnet ef database update imballato in un standalone .exe.

Perché i pacchetti vincono:

  • Nessuna dipendenza dal runtime - L'obiettivo non ha bisogno di SDK o EF CLI
  • Separazione corretta - App non ha mai bisogno db_owner; solo CI runner lo fa, solo durante l'implementazione
  • Visibilità IC - Fallimenti mostrano in log di pipeline, non sepolto in app startup
  • Sicurezza in caso di ribaltamento - La migrazione fallisce? Il deployment si ferma prima delle distribuzioni di codice errato
  • Idempotente - Tracce ciò che è applicato, funziona solo ciò che è necessario

Nota: Per la sicurezza di produzione, uso Identità gestita Invece delle stringhe di connessione. Ma i pacchetti sono ancora un passo avanti rispetto alle migrazioni runtime.

Esempio di azioni GitHub

      - name: Install EF Core tools
        run: dotnet tool install --global dotnet-ef

      - name: Add EF tools to PATH
        run: echo "$HOME/.dotnet/tools" >> $GITHUB_PATH

      - name: Generate EF migration bundle
        run: |
          dotnet ef migrations bundle \
            --project ${{ env.WEB_PROJECT }} \
            --output efbundle.exe \
            --configuration ${{ env.BUILD_CONFIGURATION }} \
            --runtime ${{ env.RUNTIME_IDENTIFIER }} \
            --context AdminDbContext \
        env:
          AdminSite__ConnectionString: ${{ secrets.PROD_SQL_CONNECTIONSTRING }}

      - name: Run EF migration bundle
        run: |
          ./efbundle.exe
        env:
          AdminSite__ConnectionString: ${{ secrets.PROD_SQL_CONNECTIONSTRING }}

Il bundle legge le stringhe di connessione dalle variabili di ambiente e applica le migrazioni pendenti. Già applicato? Esce con successo.

Bundles locali

Vuoi testare prima di spingere? Costruisci bundle localmente.

Casi di utilizzo: Prova prima di CI, DBA handoff (exe autonomo, nessun SDK necessario), schieramento, debug con --verbose.

Creazione di un bundle

# Install EF CLI (once)
dotnet tool install --global dotnet-ef

# Basic bundle
dotnet ef migrations bundle \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid \
    --output efbundle.exe

# Self-contained (includes runtime - portable to machines without .NET)
dotnet ef migrations bundle \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid \
    --output efbundle.exe \
    --self-contained

# Cross-platform (e.g., build on Windows, deploy to Linux)
dotnet ef migrations bundle \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid \
    --output efbundle \
    --runtime linux-x64

Eseguire il tuo Bundle

# Using default connection string from appsettings.json
./efbundle.exe

# Override with a specific connection string
./efbundle.exe --connection "Host=localhost;Database=mostlylucid;Username=postgres;Password=secret"

# Using an environment variable (matches your config key)
$env:ConnectionStrings__DefaultConnection="Host=localhost;..." # PowerShell
export ConnectionStrings__DefaultConnection="Host=localhost;..." # Bash
./efbundle.exe

Opzioni Bundle utili

# See what migrations would be applied without running them
./efbundle.exe --dry-run

# Verbose output for debugging
./efbundle.exe --verbose

# Apply migrations up to a specific migration (useful for testing)
./efbundle.exe --target-migration "20231115_AddUserTable"

# Combine options
./efbundle.exe --verbose --dry-run

Flusso di lavoro di prova locale

# 1. Create migration
dotnet ef migrations add AddNewFeature \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid

# 2. Build bundle
dotnet ef migrations bundle \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid \
    --output efbundle.exe

# 3. Dry run first
./efbundle.exe --dry-run --verbose

# 4. Run for real
./efbundle.exe --verbose

# 5. Broken? Remove and retry
dotnet ef migrations remove \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid

Errori di sintassi delle catture, violazioni dei vincoli, problemi FK - tutti prima CI o produzione.

Crea prestazioni

La generazione del bundle è lenta - 30+ secondi su grandi progetti. Non generare su ogni build.

  • Genera manualmente quando si testa localmente
  • Generare in CI solo durante l'implementazione, non tutti i PR
  • Pacchetti cache se le migrazioni non sono cambiate

Se si desidera davvero auto-generazione, aggiungere un obiettivo MSBuild:

<Target Name="BuildMigrationBundle">
  <Exec Command="dotnet ef migrations bundle --output $(OutputPath)efbundle.exe --force" />
</Target>

Poi: dotnet build -t:BuildMigrationBundle

Approccio ibrido

Il meglio di entrambi i mondi: convenienza a livello locale, sicurezza nella produzione.

if (builder.Environment.IsDevelopment())
{
    using var scope = app.Services.CreateScope();
    var context = scope.ServiceProvider.GetRequiredService<IMostlylucidDBContext>();
    await context.Database.MigrateAsync();
}
// Production: CI pipeline runs the bundle

Alternative ai Bundles

Script SQL

Generare SQL semplice invece di un eseguibile. Ottimo per la revisione DBA e i processi di gestione dei cambiamenti esistenti.

# All migrations
dotnet ef migrations script --output migrations.sql

# Idempotent (safe to run multiple times) - USE THIS
dotnet ef migrations script --idempotent --output migrations.sql

# Range of migrations
dotnet ef migrations script FromMigration ToMigration --output migrations.sql

Pro: Visibilità completa, qualsiasi client SQL può eseguirlo, controllo di versione amichevole, flussi di lavoro di approvazione DBA.

Punti negativi: Nessuna tracciatura automatica (uso --idempotent), esecuzione manuale, potenziale deriva se gli script vengono modificati.

Vedi documenti ufficiali sugli script SQL.

Script SQL in CI

- name: Generate and apply migrations
  run: |
    dotnet ef migrations script --idempotent --output migrations.sql
    # SQL Server
    sqlcmd -S ${{ secrets.DB_SERVER }} -d ${{ secrets.DB_NAME }} -i migrations.sql
    # Or PostgreSQL
    PGPASSWORD=${{ secrets.DB_PASSWORD }} psql -h ${{ secrets.DB_HOST }} -f migrations.sql

DACPAC (Solo server SQL)

DACPACsCity name (optional, probably does not need a translation) sono stato-based non basato sulla migrazione. Definisci lo schema desiderato, e SqlPackage lo differenzia dal database di destinazione.

SqlPackage.exe /Action:Publish /SourceFile:MyDatabase.dacpac /TargetConnectionString:"..."

Pro: Schema come codice, generazione auto-diff, gestisce tutto (tavoli, viste, SP, indici), strumenti aziendali.

Punti negativi: Solo SQL Server, schema in due posizioni (modello EF + progetto SQL), il motore diff fa scelte discutibili, le rinominazioni delle colonne sembrano drop+add.

Vedi Documenti SqlPackage.

Tabella di confronto

Avvicinamento Migliore per Richiede .NET Auto-tracks Applied DBA Friendly Cross-platform DB
MigrateAsync() Dev/small projects Yes (runtime) Yes No Yes
Bundles EF Ci/CD pipelines No (self-contained) Somewhat
Script SQL Ambienti controllati DBA No Con --idempotent
DACPAC SQL Server enterprise No Yes (state-based) Yes No

Consigli

The Designer File Gotcha

Le migrazioni funzionano localmente ma non in CI? Controllare di aver commesso entrambi i file:

  • 20231115_AddUserTable.cs - Il codice di migrazione
  • 20231115_AddUserTable.Designer.cs - L'istantanea del modello

Manca il file Designer = guasto silenzioso.

Multipli DbContexts

dotnet ef migrations bundle --context BlogDbContext --output blog-migrations.exe
dotnet ef migrations bundle --context IdentityDbContext --output identity-migrations.exe

Priorità stringa di connessione

  1. --connection argomento
  2. Variabile ambiente
  3. appsettings.json

Usare variabili d'ambiente in CI.

IDesignTimeDbContextFactory

Gli strumenti EF devono istantiare il DbContext. Se il DbContext è in un progetto separato o ha un avvio complesso, implementare IDesignTimeDbContextFactory<T>:

public class AdminDbContextFactory : IDesignTimeDbContextFactory<AdminDbContext>
{
    public AdminDbContext CreateDbContext(string[] args)
    {
        var config = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: true)
            .AddEnvironmentVariables()
            .AddUserSecrets<AdminDbContextFactory>()
            .Build();

        var connectionString = config["AdminSite:ConnectionString"]
            ?? throw new InvalidOperationException("Missing connection string");

        var optionsBuilder = new DbContextOptionsBuilder<AdminDbContext>();
        optionsBuilder.UseSqlServer(connectionString, sql => sql.CommandTimeout(120));

        return new AdminDbContext(optionsBuilder.Options);
    }
}

Utilizzare quando: DbContext in un progetto separato, startup complessa, bisogno di segreti utente per il design-time.

Che mi dici di...?

Domande comuni e risposte che ho ricevuto.

"Perché non correre dotnet ef database update in CI?"

Coperto sopra, ma la versione breve: bundle sono artefatti portatili. Il vostro passaggio di distribuzione non ha bisogno di EF CLI, codice sorgente, o la risoluzione del tempo di progettazione. Stesso bundle funziona in test, staging, e prod - zero deriva.

"Non è eccessivo per una piccola app?"

Se sei da solo, i dati sono pubblici, e il raggio d'esplosione e' basso... MigrateAsync() Ma nel momento in cui si aggiunge un secondo sviluppatore, dati sensibili, o più ambienti, pacchetti pagare per se stessi.

"Che mi dici dei rollback?"

EF non fa rollback automatici. Opzioni:

  • Genera a Down() migrazione ed eseguirlo (ma devi averlo scritto)
  • Ripristina dal backup
  • Scrivi una migrazione manuale per annullare le modifiche

Per i sistemi critici: testare le migrazioni contro un clone di database prima.

"Posso eseguire le migrazioni in un contenitore Kubernetes init?"

Sì. Bundle + contenitore init è un modello solido:

initContainers:
  - name: migrate
    image: myapp:latest
    command: ["./efbundle.exe"]
    env:
      - name: ConnectionStrings__Default
        valueFrom:
          secretKeyRef:
            name: db-secrets
            key: connection-string

Il contenitore dell'app attende che l'init completi.

"Che dire di FluentMigrator / DbUp / altri strumenti?"

Funzionano alla grande. I fasci EF sono la soluzione EF-native, ma FluentMigrator e DbUp hanno i loro fan. La differenza chiave: quelli sono strumenti specifici per la migrazione, mentre i pacchetti EF provengono dal modello EF esistente.

"Il mio DBA vuole rivedere tutto SQL prima che funzioni"

Uso --idempotent script:

dotnet ef migrations script --idempotent --output migrations.sql

DBA recensioni e approva. Quindi:

  • Eseguire lo script manualmente, o
  • Una volta approvato, eseguire il pacchetto (che fa la stessa cosa)

"Come faccio a gestire le migrazioni con zero tempi di inattività?"

E' una questione di strategia di spiegamento, non una questione di migrazioni.

  1. Rendi le migrazioni compatibili all'indietro (aggiungi colonne annullabili, non rinominare)
  2. Distribuisci nuovo codice che gestisce sia il vecchio che il nuovo schema
  3. Esegui migrazione
  4. Distribuisci codice che usa solo il nuovo schema
  5. Pulisci (fai cadere le vecchie colonne in una successiva migrazione)

Bundles non risolvere questo - fanno solo il passo 3 più prevedibile.

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.