Back to "Du gör förmodligen EF Migrations fel..."

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

Du gör förmodligen EF Migrations fel...

Sunday, 23 November 2025

Kör MigrateAsync() på start? Du ger din app databas ägare rättigheter och hoppas att ingenting går fel. Det finns ett bättre sätt - EF migration buntar låter dig köra migreringar som en kontrollerad CI steg, hålla din produktions app säker. Men här är grejen: ibland är "fel" sätt faktiskt bra. Låt oss undersöka när man ska använda varje strategi.

Officiella dokument: Översikt över migrationer | Att tillämpa migreringar | Bundor

Den "fel" sätt (som jag använder)

Den här bloggen använder MigrateAsync() Det är därför det är okej för mig, och varför det förmodligen inte är för dig.

I min Program.cs Fil Jag har följande:

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

MigrateAsync() gäller pågående migreringar och skapar databasen vid behov. Enkelt - men problematiskt:

  1. Uppstartsberoende Din app kommer inte att starta.
  2. Säkerhetsöverträdelse - Din app behöver db_owner Du gav din runtime app nycklarna till att släppa bord.

Varför jag kommer undan med det: offentliga data, enda Docker nätverk, personliga projekt. Det kan du nog inte.

När flyttningar i realtid är bra

  • Lokal utveckling - Snabb iteration slår ceremonin
  • Personliga projekt - Låg explosionsradie, inga känsliga data.
  • Docker-komposera dev miljöer -Lycka till.
  • Prototypning - Schemat förändras hela tiden ändå

När de inte är det

  • Flera app-instanser - Tävlingsförhållanden galore
  • Känsliga uppgifter - PII, finansiell, reglerad = korrekt separation krävs
  • Produktion med verkliga användare - Misslyckad migration = avbrott

Den rätta vägen: EF Bundles

En EF bunt är en fristående körbar som innehåller dina kompilerade migreringar. dotnet ef database update förpackade i ett fristående .exe.

Varför buntar vinner:

  • Inga driftberoenden - Målet behöver inte SDK eller EF CLI.
  • Korrekt separation - App behöver aldrig db_owner; endast CI löpare gör, endast under utplacering
  • CI- synlighet - Misslyckanden visas i rörledning loggar, inte begravd i app start
  • Säkerhet vid vältning - Migrationen misslyckas?
  • Befogenhet - Spårar vad som tillämpas, körs bara vad som behövs

Anmärkning: För säkerhet i produktionsledet, användning Hanterad identitet Men buntar är fortfarande ett stort steg upp från runtime migreringar.

Exempel på GitHub- åtgärder

      - 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 }}

Bunten läser anslutningssträngar från miljövariabler och tillämpar väntande migreringar. Redan tillämpad? Det bara avslutar framgångsrikt.

Lokala buntar

  • Vill du testa innan du trycker?

Användningsfall: Test före CI, DBA-avlämning (självförsörjande exe, ingen SDK behövs), iscensättningar, felsökning med --verbose.

Skapa en bunt

# 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

Köra din bundel

# 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

Användbara paketalternativ

# 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

Local Testing Workflow (lokalt arbetsflöde)

# 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

Fångar syntaxfel, begränsningsbrott, FK-problem - alla före CI eller tillverkning.

Bygg prestanda

Bundelgenerationen är långsam - 30 sekunder på stora projekt. generera inte på varje byggnad.

  • Generera manuellt vid testning lokalt
  • Generera i CI endast under distribution, inte varje PR
  • Cache buntar om migreringar inte har förändrats

Om du verkligen vill ha automatisk generation, lägg till ett MSBuild-mål:

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

Därefter gäller följande: dotnet build -t:BuildMigrationBundle

Hybridmetod

Bäst av båda världarna: bekvämlighet lokalt, säkerhet i produktionen.

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

Alternativ till bundlar

SQL- skript

Generera vanlig SQL istället för en körbar. Bra för DBA översyn och befintliga förändringshanteringsprocesser.

# 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

Förmåner: Full synlighet, alla SQL-klienter kan köra den, versionskontroll vänliga, DBA-godkännande arbetsflöden.

Minus: Ingen automatisk spårning (använd --idempotent), manuell körning, potentiell drift om skript ändras.

Se också officiella dokument om SQL- skript.

SQL- skript i 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

DAVSAC (Endast SQL- server)

AVS-staterna är Tillståndsbaserad inte Migrationsbaserad. Du definierar önskat schema, och SqlPackage diffs det mot målet databasen.

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

Förmåner: Schema som kod, automatisk diff-generering, hanterar allt (tabeller, vyer, SPs, index), företagsverktyg.

Minus: Endast SQL Server, schema på två ställen (EF-modeller + SQL-projekt), diff-motorn gör tvivelaktiga val, kolumn omnamn ser ut som drop+add.

Se också SqlPackage- dokument.

Jämförelsetabell

Tillvägagångssätt på bästa sätt för att kräva .NET på Auto-tracks Tillämpas DBA Friendly på plattform DB |----------|----------|---------------|---------------------|--------------|-------------------| | MigrateAsync() Dev/små projekt Ja (körtid) Ja Nej Ja Ja på EF-bundlar på CI/CD-rörledningar på Nej (självförsörjande) på något sätt Ja på SQL-skript, DBA-kontrollerade miljöer, Nej, med --idempotent "Ja, ja, ja, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej, nej! DAVSAC på SQL Server-företag Nej på plats Ja (state-based) på plats Ja Nej

Tips

Designerfilen Gotcha

Migration fungerar lokalt men inte inom KI? Kontrollera att du använde båda filerna:

  • 20231115_AddUserTable.cs - Migrationskoden
  • 20231115_AddUserTable.Designer.cs - Modellögonblicksbild

Saknar Designer-filen = tyst fel.

Flera DbContexts

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

Anslutningssträngsprioritet

  1. --connection argumentering
  2. Miljövariabel
  3. appsettings.json

Använd miljövariabler i KI.

IDesignTimeDbContextFactory

EF- verktyg behöver för att direktiate din DbContext. Om din DbContext är i ett separat projekt eller har komplexa start, genomföra 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);
    }
}

Använd när: DbContext i separat projekt, komplex start, behöver användarhemligheter för design-tid.

Hur är det med...?

Vanliga frågor och motgångar har jag fått.

"Varför inte bara springa dotnet ef database update I CI?"

Täckt ovan, men den korta versionen: buntar är bärbara artefakter. Din distribution steg behöver inte EF CLI, källkod, eller design-tidsupplösning. Samma bunt körs i test, iscensättning, och prod - noll drift.

"Är inte det här för mycket för en liten app?"

Om du är solo är data allmänt tillgänglig och explosionsradie är låg. MigrateAsync() är bra. Men när du lägger till en andra utvecklare, känslig data, eller flera miljöer, buntar betalar för sig själva.

"Hur är det med rollbackar?"

EF gör inte automatiska upprullningar.

  • Generera en Down() migration och köra den (men du måste ha skrivit den)
  • Återställ från säkerhetskopiering
  • Skriv en manuell migrering för att ångra ändringar

För kritiska system: testa migreringar mot en databasklon först.

"Kan jag köra migrän i en Kubernetes container?"

Ja. Bundle + init behållare är ett fast mönster:

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

App behållare väntar på init att slutföra.

"Vad sägs om FluentMigrator / DbUp / andra verktyg?"

De fungerar bra. EF buntar är EF-nativ lösning, men FluentMigrator och DbUp nyckelskillnaden: de är migrationsspecifika verktyg, medan EF-paket kommer från din befintliga EF-modell.

"Min DBA vill granska alla SQL innan det körs"

Användning --idempotent skript:

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

DBA granskar och godkänner. Sedan antingen:

  • Kör skriptet manuellt, eller
  • När det godkänts, kör bunten (som gör samma sak)

"Hur hanterar jag mig själv med noll driftstopp?"

Det är en utplaceringsstrategi, inte en migrationsfråga.

  1. Gör migreringar bakåtkompatibla (lägg till kolumner ogiltiga, byt inte namn)
  2. Använd ny kod som hanterar både gammalt och nytt schema
  3. Kör migrering
  4. Deploy kod som endast använder nytt schema
  5. Städa upp (släpp gamla kolumner i en senare migrering)

Bundles löser inte det här - de gör bara steg 3 mer förutsägbara.

logo

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