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

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.

<datetime class="hidden">2025-11-23T18:39</datetime>

<!--category--  Entity Framework, Migrations, GitHub, CI -->
**Officiella dokument:** [Översikt över migrationer](https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/) | [Att tillämpa migreringar](https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/applying) | [Bundor](https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/applying?tabs=dotnet-core-cli#bundles)

[TOC]

# 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:

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

[`MigrateAsync()`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.relationaldatabasefacadeextensions.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](https://learn.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview) Men buntar är fortfarande ett stort steg upp från runtime migreringar.

## Exempel på GitHub- åtgärder

```yaml
      - 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

```bash
# 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

```bash
# 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

```bash
# 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)

```bash
# 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:

```xml
<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.

```csharp
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.

```bash
# 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](https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/applying?tabs=dotnet-core-cli#sql-scripts).

### SQL- skript i CI

```yaml
- 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](https://learn.microsoft.com/en-us/sql/relational-databases/data-tier-applications/data-tier-applications) är *Tillståndsbaserad* inte *Migrationsbaserad*. Du definierar önskat schema, och SqlPackage diffs det mot målet databasen.

```bash
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](https://learn.microsoft.com/en-us/sql/tools/sqlpackage/sqlpackage).

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

```bash
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>`](https://learn.microsoft.com/en-us/ef/core/cli/dbcontext-creation?tabs=dotnet-core-cli#from-a-design-time-factory):

```csharp
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:

```yaml
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](https://fluentmigrator.github.io/) och [DbUp](https://dbup.readthedocs.io/) 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:

```bash
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.