Je doet waarschijnlijk EF Migrations verkeerd... (Nederlands (Dutch))

Je doet waarschijnlijk EF Migrations verkeerd...

Sunday, 23 November 2025

//

10 minute read

Uitvoeren MigrateAsync() bij het opstarten? U geeft uw app database eigenaar rechten en hopen dat er niets mis gaat. Er is een betere manier - EF migratie bundels kunt u migraties uitvoeren als een gecontroleerde CI stap, het houden van uw productie app veilig. Maar hier is het ding: soms is de "verkeerde" manier is eigenlijk prima. Laten we verkennen wanneer om elke aanpak te gebruiken.

Officiële documenten: Overzicht migraties | Migratie toepassen | Bundelsunit synonyms for matching user input

De "verkeerde" manier (dat ik gebruik)

Deze blog gebruikt MigrateAsync() bij het opstarten - de aanpak die ik je ga vertellen om niet te gebruiken. Hier is waarom dat oké is voor mij, en waarom het waarschijnlijk niet voor jou is.

In mijn Program.cs bestand Ik heb het volgende:

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

MigrateAsync() van toepassing in afwachting van migraties en maakt de database indien nodig. Simpel - maar problematisch:

  1. Opstartafhankelijkheid Je app start niet.
  2. Beveiligingsovertreding - Uw app heeft nodig db_owner Je gaf je runtime app de sleutels om tafels te laten vallen.

Waarom ik ermee weg kom: publieke data, een enkel Docker netwerk, persoonlijk project. Dat kun je waarschijnlijk niet.

Wanneer Runtime migraties zijn prima

  • Lokale dev - Snelle iteratie is beter dan ceremonie
  • Persoonlijke projecten - Lage straal, geen gevoelige gegevens.
  • Docker-compose dev omgevingen - Gemak wint
  • Prototypering - Schema verandert toch constant.

Als ze dat niet zijn.

  • Meerdere app-instances - Raceomstandigheden in overvloed
  • Gevoelige gegevens - PII, financieel, gereguleerd = juiste scheiding vereist
  • Productie met echte gebruikers - Mislukte migratie = uitval

De juiste manier: EF-bundels

Een EF-bundel is een zelfstandig uitvoerbaar bestand dat uw gecompileerde migraties bevat. dotnet ef database update verpakt in een standalone .exe.

Waarom bundels winnen:

  • Geen runtime afhankelijkheden - Doel heeft geen SDK of EF CLI nodig
  • Juiste scheiding - App heeft nooit nodig db_owner; alleen CI runner doet, alleen tijdens implementatie
  • Zichtbaarheid van CI - Mislukkingen tonen in pijplijn logs, niet begraven in app opstarten
  • Veiligheid van de terugrol - Migratie mislukt? Implementatie stopt voordat slechte code inzet
  • Idempotent - Tracks wat wordt toegepast, draait alleen wat nodig is

Opmerking: Voor de beveiliging van productiekwaliteit, gebruik Beheerde identiteit Maar bundels zijn nog steeds een belangrijke stap hoger dan runtime migraties.

Voorbeeld van GitHub-acties

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

De bundel leest verbindingsstrings van omgevingsvariabelen en past in afwachting van migraties toe. Reeds toegepast? Het sluit gewoon succesvol af.

Lokale bundels

Geen CI? Wilt u testen voordat u duwt? Bouw bundels lokaal.

Gebruiks gevallen: Test vóór CI, DBA-handoff (zelfingesloten exe, geen SDK nodig), ensceneringen, debuggen met --verbose.

Een bundel aanmaken

# 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

Running Your 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

Nuttige bundelopties

# 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

Lokale testworkflow

# 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

Vangst syntaxis fouten, beperkingen schendingen, FK problemen - alle voor CI of productie.

Bouwprestaties

Bundelgeneratie is traag - 30+ seconden op grote projecten. Niet genereren op elk gebouw.

  • Handmatig genereren bij lokaal testen
  • Genereren in CI alleen tijdens implementatie, niet elke PR
  • Cache bundels als migraties niet zijn veranderd

Als je echt auto-generatie wilt, voeg dan een MSBuild target toe:

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

Dan: dotnet build -t:BuildMigrationBundle

Hybride aanpak

Beste van beide werelden: gemak lokaal, veiligheid in de productie.

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

Alternatieven voor bundels

SQL-scripts

Genereer gewone SQL in plaats van een uitvoerbaar programma. Geweldig voor DBA-evaluatie en bestaande veranderingsbeheerprocessen.

# 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

Voordelen: Volledige zichtbaarheid, elke SQL client kan uitvoeren, versie controle vriendelijk, DBA goedkeuring workflows.

Nadelen: Geen auto-tracking (gebruik --idempotent), handmatige uitvoering, potentiële drift als scripts worden gewijzigd.

Zie officiële documenten over SQL-scripts.

SQL Scripts 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

DACAC (alleen SQL-server)

DACCAC's zijn state-based niet migratie-gebaseerd. U definieert het gewenste schema, en SqlPackage diffeert het met de doeldatabase.

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

Voordelen: Schema als code, auto-diff generatie, behandelt alles (tabellen, weergaven, SP's, indexen), enterprise tooling.

Nadelen: SQL Server alleen, schema op twee plaatsen (EF-modellen + SQL project), diff engine maakt twijfelachtige keuzes, kolomhernoemingen lijken op drop+add.

Zie SqlPackage docs.

Vergelijkingstabel

Aanpak Best Voor .NET vereist Auto-tracks Toegepast DBA Vriendelijk Cross-platform DB |----------|----------|---------------|---------------------|--------------|-------------------| | MigrateAsync() Dev/kleine projecten Ja (runtime) Ja EF-bundels - CI/CD-pijpleidingen - Nee (zelfvoorzienend) - Ja -- Ja -- Ja -- Ja SQL Scripts DBA-gecontroleerde omgevingen --idempotent Ja Ja Ja DACAC SQL Server enterprise Nee Ja (state-based) Ja Nee

Tips

Het Designer-bestand Hebbes

Migratie werkt lokaal, maar niet in CI? Controleer of je beide bestanden hebt vastgelegd:

  • 20231115_AddUserTable.cs - De migratiecode.
  • 20231115_AddUserTable.Designer.cs - De model snapshot

Ontbreken van het Designer bestand = stil falen.

Meerdere DbContexts

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

Verbindingstekenreeksprioriteit

  1. --connection argument
  2. Omgevingsvariabele
  3. appsettings.json

Gebruik omgevingsvariabelen in CI.

IDesignTimeDbContextFactory

EF-tools moeten uw DbContext instantiëren. Als uw DbContext in een apart project zit of complexe opstart heeft, implementeren 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);
    }
}

Gebruik wanneer: DbContext in apart project, complexe opstart, User Secrets nodig hebben voor ontwerp-tijd.

Hoe zit het met...?

Veelgestelde vragen en terugval die ik heb ontvangen.

"Waarom niet gewoon rennen dotnet ef database update in CI?"

Overdekt boven, maar de korte versie: bundels zijn draagbare artefacten. Uw implementatie stap hoeft niet EF CLI, broncode, of ontwerp-tijd resolutie. Dezelfde bundel draait in test, enscenering, en prod - nul drift.

"Is dit niet overkill voor een kleine app?"

Als je alleen bent, is de data openbaar en is de straal laag... MigrateAsync() Maar zodra je een tweede ontwikkelaar, gevoelige gegevens of meerdere omgevingen toevoegt, betalen bundels voor zichzelf.

"Hoe zit het met terugdraaien?"

EF doet geen automatische terugrol. Opties:

  • Genereren a Down() migratie en voer het uit (maar je moet het hebben geschreven)
  • Herstellen van back-up
  • Schrijf een handmatige migratie om wijzigingen ongedaan te maken

Voor kritieke systemen: eerst de migratie testen op een databasekloon.

"Kan ik migraties uitvoeren in een Kubernetes init container?"

Ja. Bundel + init container is een solide patroon:

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

App container wacht tot init klaar is.

"Hoe zit het met FluentMigrator / DbUp / andere tools?"

Ze werken geweldig. EF bundels zijn de EF-native oplossing, maar FluentMigrator en DbUp belangrijk verschil: dat zijn migratie-specifieke tools, terwijl EF-bundels afkomstig zijn van uw bestaande EF-model.

"Mijn DBA wil alle SQL bekijken voordat het draait"

Gebruik --idempotent scripts:

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

DBA beoordelingen en keurt het goed.

  • Het script handmatig uitvoeren, of
  • Eenmaal goedgekeurd, voer de bundel (die hetzelfde doet)

"Hoe kan ik omgaan met migraties met nul stilstand?"

Dat is een inzet strategie vraag, geen migratie vraag.

  1. Maak migraties achterstevoren compatibel (voeg kolommen nullable toe, hernoem niet)
  2. Implementeer nieuwe code die zowel oude als nieuwe schema's behandelt
  3. Migratie uitvoeren
  4. Implementeer code die alleen nieuwe schema's gebruikt
  5. Opruimen (oude kolommen in een latere migratie laten vallen)

Bundels lossen dit niet op - ze maken stap 3 gewoon voorspelbaarer.

Finding related posts...
logo

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