Olet luultavasti tekemässä EF-siirtolaisuutta väärin... (Suomi (Finnish))

Olet luultavasti tekemässä EF-siirtolaisuutta väärin...

Sunday, 23 November 2025

//

9 minute read

Suoritetaan MigrateAsync() Startupissa? Annat sovellustietokannan omistajalle oikeudet ja toivot, että mikään ei mene pieleen. On olemassa parempi tapa - EF migration -nippujen avulla voit pyörittää muuttoliikkeitä kontrolloituna CI-askeleena ja pitää tuotantosovelluksesi turvallisena. Mutta joskus "väärä" tapa on itse asiassa hyvä. Tutkitaan, milloin kannattaa käyttää jokaista lähestymistapaa.

Viralliset dokumentit: Maahanmuuttoa koskeva yleiskatsaus | Muuttoliikkeiden soveltaminen | Punokset

"Väärin" tapa (jota käytän)

Tämä blogi käyttää MigrateAsync() Startup - lähestymistapa, jota aion kieltää käyttämästä. Tämän vuoksi se sopii minulle, ja miksi se ei todennäköisesti ole sinulle.

Omassa Program.cs Minulla on seuraavat tiedot:

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

MigrateAsync() soveltaa vireillä olevia siirtoja ja luo tarvittaessa tietokannan. Yksinkertainen - mutta ongelmallinen:

  1. Käynnistysriippuvuus Sovelluksesi ei käynnisty.
  2. Turvallisuusrikkomus - Sovelluksesi tarvitsee db_owner Annoit juuri aika-applikaatiollesi avaimet pudottaa pöytiä.

Miksi pääsen pälkähästä: julkinen data, yksittäinen Docker-verkko, henkilökohtainen projekti. Et varmaankaan pysty.

Kun nopeat muuttoliikkeet ovat kunnossa

  • Paikallinen dev - Nopea iterointi voittaa seremonian
  • Henkilökohtaiset projektit - Alhainen räjähdyssäde, ei arkaluontoisia tietoja
  • Docker-compose-devo-ympäristöt Kätevyys voittaa
  • Prototyypitys - Schema muuttuu joka tapauksessa jatkuvasti

Kun he eivät ole

  • Useita applikaatioita - Rotuolosuhteet galore
  • Herkät tiedot - PII, taloudellinen, säännelty = asianmukainen erottaminen
  • Tuotanto todellisilla käyttäjillä - Epäonnistunut muutto = käyttökatkos

Oikea tapa: EF Bundles

EF-nippu on omatoiminen suoritin, joka sisältää kootut muuttoliikkeesi. dotnet ef database update pakataan itsenäiseksi .exe.

Miksi nippuja voittaa:

  • Ei ajoajan riippuvuuksia - Kohde ei tarvitse SDK:ta tai EF CLI:tä
  • Asianmukainen erottaminen - Sovellus ei koskaan tarvitse db_owner; vain CI-juoksijalla on, vain käyttöönoton aikana
  • CI-näkyvyys - Epäonnistumiset näkyvät putkiloissa, eivät sovelluksen käynnistykseen hautautuneina
  • Kääntymisturvallisuus Maahanmuutto epäonnistuu, ennen kuin huonot koodit alkavat.
  • Idempotentti - Seuraa mitä sovelletaan, kulkee vain mitä tarvitaan

Huomaa: Tuotantoluokan varmuuteen, käyttöön Hallittu henkilöllisyys Kytkentänarujen sijaan, mutta nippu on yhä merkittävä askel eteenpäin.

GitHub-toiminnot esimerkki

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

Nippu lukee yhteyden naruja ympäristömuuttujista ja soveltaa vireillä olevia muuttoliikkeitä. Jo käytössä? Se vain poistuu onnistuneesti.

Paikalliset bundlet

Ei tiedonantajaa, haluatko testata ennen työntämistä? Rakenna nippuja paikallisesti.

Käyttötapaukset: Testaa ennen CI:n, DBA:n luovutusta (itse asiassa exe, ei SDK:ta tarvita), lavastusten käyttöönottoa, vianetsintää --verbose.

Bundlen luominen

# 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

Bundlen pyörittäminen

# 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

Hyödyllisiä bundle-vaihtoehtoja

# 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

Paikallinen testaus Työnkulku

# 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

Nappaa syntaksivirheitä, rajoitusrikkomuksia, FK-asioita - kaikki ennen CI tai tuotanto.

Rakenna suorituskykyä

Bundle-sukupolvi on hidas - 30 sekuntia isoissa projekteissa.

  • Luo käsin, kun testaat paikallisesti
  • Luo CI:ssä vain käyttöönoton aikana, ei jokaisen PR:n aikana
  • Välimuistin nippuja, jos muuttoliikkeet eivät ole muuttuneet

Jos todella haluat autogeneraatiota, lisää MSBuild-kohde:

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

Sitten: dotnet build -t:BuildMigrationBundle

Hybridilähestymistapa

Paras molemmista maailmoista: mukavuus paikallisesti, tuotannon turvallisuus.

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

Vaihtoehtoja bundleille

SQL-skriptit

Luo tavallinen SQL suoritettavan sijaan. Loistava DBA-arviointiin ja olemassa oleviin muutoshallintaprosesseihin.

# 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

Plussat: Täysi näkyvyys, mikä tahansa SQL-asiakas voi ajaa sitä, versiohallinta ystävällinen, DBA-hyväksynnän työnkulku.

Miinukset: Ei automaattijäljitystä (käytä --idempotent), manuaalinen suoritus, mahdollinen drift, jos skriptejä muutetaan.

Katso viralliset dokumentit SQL-skripteistä.

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

DACTAC (vain SQL-palvelin)

DACPAC-keskukset a valtiolähtöinen ei Muuttoliikkeeseen perustuvaMäärität halutun skeeman, ja SqlPackage vertaa sitä kohdetietokantaan.

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

Plussat: Schema koodina, automaattisena diff-sukupolvena, hoitaa kaiken (taulukot, näkymät, SP:t, indeksit), yritystyökalut.

Miinukset: Vain SQL Server, schema kahdessa paikassa (EF-mallit + SQL-projekti), diff-moottori tekee kyseenalaisia valintoja, sarake uudelleennimet näyttävät pudotus+lisältä.

Katso SqlPackage-dokumentit.

Vertailutaulukko

Autoraiteet Sovellettava DBA Friendly Cross-platform DB |----------|----------|---------------|---------------------|--------------|-------------------| | MigrateAsync() Kyllä, kyllä, kyllä, kyllä, kyllä EF Bundles CI/CD-putkistot SQL:n käsikirjoitukset DBA:n hallitsemissa ympäristöissä --idempotent Kyllä, kyllä, kyllä. DACPAC, SQL Server enterprise Yes (valtiollinen) Kyllä

Vinkkejä

Suunnittelija-tiedosto Gotcha

Muuttoliikkeet toimivat paikallisesti, mutta eivät tiedonantajana? Tarkista, että kirjoitit molemmat tiedostot:

  • 20231115_AddUserTable.cs - Maahanmuuttokoodi
  • 20231115_AddUserTable.Designer.cs - Mallikuva

Designer-tiedoston puuttuminen = äänetön epäonnistuminen.

Useita DbContextejä

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

Liitännät etusijalle

  1. --connection argumentti
  2. Ympäristömuuttuja
  3. appsettings.json

Käytä ympäristömuuttujia CI:ssä.

IDesignTimeDbContextFactory

Jos DbContext on erillisessä projektissa tai siinä on monimutkainen käynnistys, toteuta 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);
    }
}

Käytä, kun: DbContext erillisessä projektissa, monimutkainen käynnistys, tarvitsee käyttäjäsalaisuuksia suunnitteluaikaan.

Entä...?

Olen saanut yleisiä kysymyksiä ja vastatoimia.

"Miksei vain juosta dotnet ef database update CI:ssä?"

Katettu yllä, mutta lyhyt versio: niput ovat kannettavia esineitä. Käyttöönottovaihe ei tarvitse EF CLI:tä, lähdekoodia tai suunnitteluajan tarkkuutta. Sama nippu kulkee testissä, lavastuksessa ja prod - nolla-ajossa.

"Eikö tämä ole liioittelua pienelle sovellukselle?"

Jos olet yksin, data on julkista, ja räjähdyssäde on matala. MigrateAsync() Mutta heti kun lisäät toisen kehittäjän, arkaluonteisen datan tai useita ympäristöjä, nippu maksaa itsensä.

"Entä rollbackit?"

EF ei tee automaattikäännöksiä.

  • Luo a Down() Muuttoliike ja sen pyörittäminen (mutta sinun täytyy olla kirjoittanut se)
  • Palauta varmuuskopiosta
  • Kirjoita manuaalinen siirtymä, jolla muutokset perutaan

Kriittisten järjestelmien osalta testataan ensin migraatiota tietokantakloonia vastaan.

"Saanko pyörittää muuttoliikkeitä Kuberneettien kontissa?"

Kyllä. Bundle + init -pakkaus on kiinteä kuvio:

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

Applikaattori odottaa, että se valmistuu.

"Entä FluentMigrator / DbUp / muut työkalut?"

Ne toimivat hyvin. EF-nippu on EF-natiivi ratkaisu, mutta FluentMigraattori sekä DbUp Avainero: ne ovat muuttoliikkeelle ominaisia työkaluja, kun taas EF-nippu on peräisin nykyisestä EF-mallistasi.

"DBA haluaa tarkistaa SQL:n ennen kuin se toimii."

Käyttö --idempotent käsikirjoitukset:

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

DBA arvioi ja hyväksyy. Sitten joko:

  • Suorita skripti käsin, tai
  • Kun se on hyväksytty, pyöritä nippua (joka toimii samoin)

"Miten käsittelen muuttoliikkeitä, joissa ei ole vapaa-aikaa?"

Se on käyttöönottostrategiakysymys, ei maahanmuuttokysymys.

  1. Tee siirtymisestä takaperin yhteensopivaa (lisää sarakkeita mitättömäksi, älä nimeä uudelleen)
  2. Ota käyttöön uusi koodi, joka käsittelee sekä vanhaa että uutta skeemaa
  3. Suorita muuttoliikkeet
  4. Käytä koodia, joka käyttää vain uutta skeemaa
  5. Siivotkaa (pudottakaa vanhat palstat myöhempään muuttoon)

Bundlet eivät ratkaise tätä, ne vain tekevät vaiheesta 3 ennustettavamman.

Finding related posts...
logo

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