Start Debugging

Vom HasData-Seeding zu UseAsyncSeeding in EF Core 11 migrieren

Eine Schritt-fuer-Schritt-Anleitung, um Seed-Daten von HasData auf UseSeeding und UseAsyncSeeding in EF Core 11 umzustellen, inklusive der DeleteData-Migrationsfalle, die Ihre vorhandenen Zeilen loescht, wenn Sie sie uebersehen.

Seed-Daten von HasData auf UseSeeding/UseAsyncSeeding in EF Core 11 zu verschieben, ist fuer eine typische Anwendung eine halbtaegige Aufgabe, und die eine Sache, die Sie beisst, ist nicht die neue API: Es ist, dass das Loeschen eines HasData-Aufrufs den naechsten dotnet ef migrations add dazu bringt, eine DeleteData-Anweisung auszugeben, die genau diese Zeilen aus jeder Datenbank entfernt, gegen die die Migration laeuft. Wenn Sie das HasData loeschen und das UseSeeding in derselben Bereitstellung hinzufuegen, ohne an die Reihenfolge zu denken, koennen Sie Referenzdaten in der Produktion loeschen. Diese Anleitung durchlaeuft die Migration in der Reihenfolge, die das vermeidet, unter Verwendung von .NET 11, EF Core 11 (Microsoft.EntityFrameworkCore 11.0) und C# 14. Planen Sie einen Nachmittag fuer eine mittelgrosse Anwendung ein, der grosste Teil davon fuer das Pruefen, welche Seeds schon vor Jahren haetten verschoben werden sollen.

Lohnt es sich? Fuer alles Dynamische, mit generiertem Schluessel, Berechnetes oder Bedingtes: ja, und es war ueberfaellig. Fuer eine wirklich statische Referenztabelle mit drei Zeilen lassen Sie sie auf HasData. Das Ziel hier ist nicht “jedes HasData loeschen”, sondern “die Seeds verschieben, die nie ins Modell gehoerten”.

Warum HasData ueberhaupt verlassen

HasData baut Ihre Seed-Daten in den Modell-Snapshot ein. Diese eine Design-Tatsache ist die Wurzel jedes Grundes, es zu verlassen:

Das EF-Team hat HasData in “model-managed data” umbenannt, gerade um von seiner Verwendung als allgemeines Seeding-Werkzeug abzuraten. UseSeeding und UseAsyncSeeding, in EF Core 9 eingefuehrt und in EF Core 11 aktuell, sind gewoehnlicher Anwendungscode, der gegen einen lebendigen DbContext laeuft, ohne einen dieser Grenzen. Wenn Sie noch entscheiden, welche Seeds Sie verschieben, zieht die HasData-vs-UseSeeding-Entscheidungsmatrix die Linie Zeile fuer Zeile.

Was bricht

BereichAenderungSchweregrad
Bestehende DatenbankenDas Entfernen eines HasData-Aufrufs erzeugt DeleteData, das die Seed-Zeilen bei der Migration loeschthoch
IdempotenzHasData wurde automatisch differenziert; UseSeeding laeuft jedes Mal und braucht eine von Hand geschriebene Existenzpruefunghoch
Zwei CodepfadeDas Tooling ruft das synchrone UseSeeding, der async-Start ruft UseAsyncSeeding; implementieren Sie nur eines, und das andere macht still nichtshoch
VersionskontrolleDie Seed-Werte verlassen den Migrations-Snapshot und leben im Startcode; sie werden nicht mehr in der Migrationshistorie erfasstmittel
Referenzielle IntegritaetDaten, von denen Fremdschluessel anderer Tabellen abhaengen, landen jetzt in einem Start-Callback, nicht innerhalb der Migrationstransaktionmittel
Test-SetupTests, die darauf angewiesen waren, dass HasData ueber EnsureCreated laeuft, funktionieren weiterhin, aber nur, wenn beide Overloads implementiert sindniedrig

Die oberste Zeile ist die, die in einem Vorfallsbericht endet. Alles andere ist mechanisch.

Pre-Flight-Checkliste

Bevor Sie eine Zeile Seeding-Code anfassen:

Migrationsschritte

Die Reihenfolge zaehlt. Dies in der falschen Sequenz zu tun, ist genau, wie Sie Referenzdaten aus der Produktion loeschen.

1. Schreiben Sie zuerst die UseSeeding- und UseAsyncSeeding-Callbacks

Fuegen Sie den neuen Seeding-Pfad hinzu, bevor Sie etwas entfernen. Beide Overloads, identische Logik, Existenzpruefung oben in jedem. Faktorisieren Sie den Rumpf in gemeinsame Methoden, damit die synchrone und die async-Version nicht auseinanderdriften:

// Program.cs -- .NET 11, ASP.NET Core 11, EF Core 11 (Microsoft.EntityFrameworkCore 11.0), C# 14
builder.Services.AddDbContext<AppDbContext>(options =>
    options
        .UseSqlServer(builder.Configuration.GetConnectionString("Default"))
        .UseSeeding((context, _) => SeedStatuses(context))
        .UseAsyncSeeding((context, _, ct) => SeedStatusesAsync(context, ct)));

static void SeedStatuses(DbContext context)
{
    string[] required = ["Pending", "Shipped", "Delivered"];
    var existing = context.Set<OrderStatus>()
        .Where(s => required.Contains(s.Name))
        .Select(s => s.Name)
        .ToHashSet();

    var missing = required
        .Where(name => !existing.Contains(name))
        .Select(name => new OrderStatus { Name = name })
        .ToList();

    if (missing.Count > 0)
    {
        context.Set<OrderStatus>().AddRange(missing);
        context.SaveChanges();
    }
}

static async Task SeedStatusesAsync(DbContext context, CancellationToken ct)
{
    string[] required = ["Pending", "Shipped", "Delivered"];
    var existing = await context.Set<OrderStatus>()
        .Where(s => required.Contains(s.Name))
        .Select(s => s.Name)
        .ToListAsync(ct);

    var missing = required
        .Where(name => !existing.Contains(name))
        .Select(name => new OrderStatus { Name = name })
        .ToList();

    if (missing.Count > 0)
    {
        context.Set<OrderStatus>().AddRange(missing);
        await context.SaveChangesAsync(ct);
    }
}

Beachten Sie, dass diese Version Id nicht mehr von Hand fest vergibt. Die Datenbank weist ihn zu. Das ist der ganze Punkt: Wenn Sie zuvor Schluessel von Hand in HasData zugewiesen haben, koennen bestehende Fremdschluessel-Referenzen auf genau diese Werte zeigen, was Schritt 2 heikel macht.

Pruefen: Fuehren Sie die Anwendung gegen eine leere lokale Datenbank aus, wobei die HasData-Aufrufe noch vorhanden sind. Sie sollte sauber starten, ohne doppelte Zeilen. Die Existenzpruefung sollte einen zweiten Start zu einem No-op machen (eine Lese-Abfrage, null Schreibvorgaenge).

2. Entscheiden Sie, wie bestehende Zeilen erhalten bleiben, bevor Sie HasData entfernen

Dies ist der Schritt, den alle ueberspringen und bereuen. Wenn Sie einen HasData-Aufruf loeschen und dotnet ef migrations add ausfuehren, erzeugt EF ein DeleteData in der Up-Methode fuer jede zuvor geseedete Zeile:

// .NET 11, EF Core 11 -- what removing HasData generates
migrationBuilder.DeleteData(
    table: "OrderStatuses",
    keyColumn: "Id",
    keyValues: new object[] { 1, 2, 3 });

Auf einer bestehenden Datenbank laeuft dieses DELETE. Wenn andere Tabellen Fremdschluessel haben, die auf diese Zeilen zeigen, schlaegt das Delete entweder mit einem Fehler FOREIGN KEY constraint failed fehl oder, schlimmer, kaskadiert. Sie haben drei sichere Optionen:

Fuer alles mit eingehenden Fremdschluesseln ist Option B fast immer, was Sie wollen. Die Zeilen muessen sich nicht bewegen; nur die Eigentuemerschaft an ihnen.

Pruefen: Lesen Sie nach dem Erzeugen der Entfernungs-Migration die generierte Datei, bevor Sie sie anwenden. Bestaetigen Sie, dass die DeleteData-Aufrufe genau die Zeilen anvisieren, die Sie erwarten, und dass Sie fuer jede Tabelle A, B oder C gewaehlt und angewendet haben.

3. Entfernen Sie die HasData-Aufrufe aus OnModelCreating

Loeschen Sie jetzt den HasData-Block fuer die Seeds, die Sie verschieben:

// Before -- .NET 11, EF Core 11
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<OrderStatus>().HasData(
        new OrderStatus { Id = 1, Name = "Pending" },
        new OrderStatus { Id = 2, Name = "Shipped" },
        new OrderStatus { Id = 3, Name = "Delivered" });
}

// After -- the HasData block is gone; seeding lives in UseSeeding now
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // OrderStatus seeding moved to UseSeeding in Program.cs
}

Pruefen: Das Projekt kompiliert weiterhin, und dotnet ef migrations has-pending-model-changes meldet eine ausstehende Aenderung (der entfernte Seed), die Sie gleich erfassen.

4. Erzeugen und pruefen Sie die Entfernungs-Migration

# .NET 11, EF Core 11
dotnet ef migrations add RemoveOrderStatusSeed

Oeffnen Sie die generierte Migration und wenden Sie Ihre Entscheidung aus Schritt 2 an. Wenn Sie Option B gewaehlt haben, loeschen Sie das DeleteData aus Up und das entsprechende InsertData aus Down. Lassen Sie die Modell-Snapshot-Aenderungen unberuehrt; diese sind korrekt und notwendig.

Pruefen: Fuehren Sie dotnet ef migrations script gegen Ihre Staging-Datenbank aus und lesen Sie das SQL. Bestaetigen Sie, dass kein unerwartetes DELETE FROM OrderStatuses ueberlebt, wenn Sie die Zeilen erhalten wollten.

5. Wenden Sie gegen Staging an und bestaetigen Sie, dass die Daten ueberleben

# .NET 11, EF Core 11
dotnet ef database update

Starten Sie dann die Anwendung, damit UseAsyncSeeding laeuft.

Pruefen: Fragen Sie die Tabelle ab. Die Referenzzeilen sind genau einmal vorhanden. Starten Sie die Anwendung ein zweites Mal und bestaetigen Sie, dass keine Duplikate aufgetaucht sind, was beweist, dass die Existenzpruefung funktioniert. Wenn Fremdschluessel die Zeilen referenzieren, bestaetigen Sie, dass diese Beziehungen intakt sind.

Rauchtest nach der Migration

Fuehren Sie diese Checkliste gegen Staging aus, bevor Sie ausliefern:

Rollback-Plan

Diese Migration ist umkehrbar, aber lesen Sie das Kleingedruckte. Wenn Sie Option B gewaehlt haben (das DeleteData von Hand herausbearbeitet), ist das schema-seitige Down fuer die Daten ein No-op, sodass das Zurueckrollen der Migration die Zeilen an Ort und Stelle laesst und einfach den Modell-Snapshot wiederherstellt. Das erneute Hinzufuegen des HasData-Blocks und das Erzeugen einer neuen Migration bringt Sie in die alte Welt zurueck, obwohl EF jede fehlende Zeile per InsertData einfuegen will.

Wenn Sie Option A gewaehlt haben (das Loeschen-und-neu-Seeden zugelassen), ist der Rollback riskanter: Die Zeilen haben jetzt von der Datenbank generierte Schluessel, sodass das Zuruecksetzen auf ein HasData-Modell, das feste Schluessel erwartet, nicht uebereinstimmt. Versuchen Sie in diesem Fall keinen In-Place-Rollback von Live-Daten. Stellen Sie aus dem Backup wieder her, das Sie im Pre-Flight-Schritt gemacht haben. Deshalb ist das Pre-Flight-Backup nicht optional.

Fallen, in die wir getappt sind

Wenn Ihre Entitaeten Records sind, aendert sich daran nichts: Records funktionieren mit EF Core 11 korrekt sowohl unter HasData als auch UseSeeding. Und wenn Sie die Datenschicht ohnehin gerade straffen, taucht dieselbe “was laeuft und wann”-Disziplin bei den EF-Core-11-Interceptoren fuer Auditing auf.

Verwandt

Quellen

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Zurück