Start Debugging

HasData vs UseSeeding fuer das Seeding von Daten in EF Core 11: was sollten Sie verwenden?

Verwenden Sie HasData nur fuer feste, modelleigene Referenzdaten. Verwenden Sie UseSeeding und UseAsyncSeeding fuer alles andere in EF Core 11. Ein direkter Vergleich mit den Regeln, die die Entscheidung erzwingen.

Verwenden Sie fuer das Seeding von Daten in EF Core 11 HasData nur fuer kleine, feste, deterministische Referenztabellen, die Sie innerhalb Ihrer Migrationen versionieren wollen (Laendercodes, Waehrungen, enum-artige Nachschlagezeilen). Verwenden Sie UseSeeding und UseAsyncSeeding fuer alles andere: alles mit datenbankgenerierten Schluesseln, berechneten oder nicht deterministischen Werten, bedingter Logik, Navigationseigenschaften oder Zeilen, die davon abhaengen, was bereits in der Datenbank steht. Das EF-Team hat HasData in “model-managed data” umbenannt, gerade um von der Verwendung als allgemeines Seeding-Werkzeug abzuraten. Dieser Artikel verwendet .NET 11, EF Core 11 (Microsoft.EntityFrameworkCore 11.0) und C# 14.

Wenn Sie sich nur eine Zeile merken: HasData ist Teil des Modells und des Migrations-Diffs, UseSeeding laeuft als gewoehnlicher Code gegen einen lebenden DbContext. Fast jeder schmerzhafte Seeding-Bug entsteht dadurch, dass man das erste waehlt, obwohl man das zweite braucht.

Die Feature-Matrix

Dies ist die Tabelle, fuer die Sie gekommen sind. Jede Zeile ist eine echte Einschraenkung, kein Gefuehl.

FeatureHasData (model-managed data)UseSeeding / UseAsyncSeeding
Konfiguriert inOnModelCreating (das Modell)DbContextOptionsBuilder
Wann es laeuftIm Migrations-SQL (InsertData)Bei Migrate/EnsureCreated und dotnet ef database update
PrimaerschluesselMuessen von Hand angegeben werdenDatenbankgenerierte Schluessel funktionieren
Nicht deterministische WerteVerboten (Re-Diff bei jedem Build)Erlaubt (Guid.NewGuid(), DateTime.UtcNow)
NavigationseigenschaftenNein, nur FremdschluesselJa, vollstaendige Graph-Inserts
Bedingte LogikNeinJa, Sie schreiben das if
Externe Aufrufe / TransformationenNeinJa (Hashing, HTTP, Dateilesen)
IdempotenzAutomatisch (EF macht den Diff)Sie schreiben die Existenzpruefung
In der Versionsverwaltung erfasstJa, im Migrations-SnapshotNein, es ist Startup-Code
Aktualisiert bestehende ZeilenJa, ueber Migrations-DiffNur wenn Sie das Update schreiben
Verfuegbar seitEF Core 1.0 (war HasData)EF Core 9, aktuell in EF Core 11

Die mit Abstand wichtigste Zeile ist “nicht deterministische Werte”. HasData wird bei jedem Build gegen den Modell-Snapshot verglichen, daher laesst ein DateTime.UtcNow oder ein Guid.NewGuid() in Ihrem Seed das Modell jedes Mal veraendert erscheinen, und EF wirft PendingModelChangesWarning oder erzeugt ein endloses Rinnsal sinnloser Migrationen. Das ist die Wand, gegen die die meisten laufen, gewoehnlich waehrend einer Migration von EF Core 6 zu EF Core 11.

Wann Sie HasData waehlen sollten

HasData ist das richtige Werkzeug, wenn die Daten wirklich Teil der Bedeutung Ihres Schemas sind. Der gedankliche Test: Waeren Sie damit zufrieden, diese Zeilen in einer Konstante zu codieren, und werden sie sich fast nie aendern?

// .NET 11, EF Core 11 (Microsoft.EntityFrameworkCore 11.0), C# 14
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" });
}

Die Schluessel muessen explizit gesetzt werden, und die Werte muessen deterministisch sein. Kein Guid.NewGuid(), kein DateTime.Now, keine Navigationseigenschaften (setzen Sie die Fremdschluesselspalte direkt). Brechen Sie eine davon, und HasData kaempft bei jedem Build mit Ihnen.

Wann Sie UseSeeding und UseAsyncSeeding waehlen sollten

UseSeeding ist der in EF Core 11 empfohlene Allzweck-Mechanismus. Es ist einfacher Anwendungscode mit einem lebenden DbContext, daher existieren die Grenzen von HasData schlicht nicht. Greifen Sie dazu, sobald eine davon zutrifft:

// .NET 11, EF Core 11 (Microsoft.EntityFrameworkCore 11.0), C# 14
services.AddDbContext<AppDbContext>(options =>
    options
        .UseSqlServer(connectionString)
        .UseAsyncSeeding(async (context, _, ct) =>
        {
            if (!await context.Set<User>().AnyAsync(u => u.Email == "admin@example.com", ct))
            {
                context.Set<User>().Add(new User
                {
                    Email = "admin@example.com",
                    PasswordHash = PasswordHasher.Hash("change-me"), // runtime transform
                    CreatedAt = DateTime.UtcNow                        // non-deterministic
                });
                await context.SaveChangesAsync(ct);
            }
        })
        .UseSeeding((context, _) =>
        {
            if (!context.Set<User>().Any(u => u.Email == "admin@example.com"))
            {
                context.Set<User>().Add(new User
                {
                    Email = "admin@example.com",
                    PasswordHash = PasswordHasher.Hash("change-me"),
                    CreatedAt = DateTime.UtcNow
                });
                context.SaveChanges();
            }
        }));

Der Haken ist der, den die meisten uebersehen: Sie muessen beide Ueberladungen implementieren, und die Existenzpruefung liegt bei Ihnen. Die EF-Core-Tools (dotnet ef database update) rufen nur das synchrone UseSeeding auf; Ihr Laufzeitpfad ruft UseAsyncSeeding auf. Implementieren Sie das eine und nicht das andere, dann tut das Seeding aus dem vergessenen Pfad stillschweigend nichts. Fuer die vollstaendigen Mechanismen siehe wie man Daten mit UseSeeding und UseAsyncSeeding seedet.

Was tatsaechlich auf der Festplatte passiert

Der klarste Weg, den Unterschied zu sehen, ist zu betrachten, was jedes erzeugt.

HasData materialisiert sich in Ihrer Migration. Nach dotnet ef migrations add SeedStatuses enthaelt die generierte Up-Methode SQL-foermige Aufrufe:

// .NET 11, EF Core 11 generated migration
migrationBuilder.InsertData(
    table: "OrderStatuses",
    columns: new[] { "Id", "Name" },
    values: new object[,]
    {
        { 1, "Pending" },
        { 2, "Shipped" },
        { 3, "Delivered" }
    });

Die Daten sind nun ein versioniertes Artefakt. Es laeuft einmal, innerhalb der Transaktion der Migration, und EF verfolgt es im Modell-Snapshot, sodass eine spaetere Aenderung ein UpdateData erzeugt. Genau deshalb ist ein nicht deterministischer Wert hier Gift: Der Snapshot wuerde nie uebereinstimmen, sodass EF glauben wuerde, das Modell habe sich bei jedem einzelnen Build geaendert.

UseSeeding erzeugt nichts auf der Festplatte. Es ist ein Callback, der ausgeloest wird, nachdem das Schema aktuell ist, bei jedem Migrate, EnsureCreated oder dotnet ef database update, einschliesslich Laeufen, bei denen keine Migration angewendet wurde. Da er bedingungslos laeuft, ist die Existenzpruefung kein optionaler Hausputz, sondern das Einzige, was zwischen Ihnen und doppelten Zeilen steht. EF Core 11 schuetzt den Callback mit demselben Migrations-Sperrmechanismus, den es fuer Migrationen verwendet, sodass zwei gleichzeitig startende Anwendungsinstanzen nicht beide den Seed nebenlaeufig ausfuehren, aber die Sperre macht ein fehlendes if nicht idempotent.

Der Haken, der fuer Sie entscheidet

Einige Einschraenkungen ueberstimmen die Praeferenz vollstaendig. Wenn eine davon zutrifft, ist die Entscheidung fuer Sie gefallen:

Sie koennen beide problemlos in einer Anwendung verwenden. Eine gaengige, gesunde Aufteilung: HasData fuer die drei Nachschlagetabellen, ohne die Ihr Schema nicht funktionieren kann, und UseSeeding fuer die Demo-Daten, den Standard-Admin und alles Tenant-spezifische. Sie geraten nicht in Konflikt, weil sie in verschiedenen Phasen laufen.

Die Empfehlung, wiederholt

Verwenden Sie standardmaessig UseSeeding und UseAsyncSeeding in EF Core 11. Sie sind der empfohlene Mechanismus, sie bewaeltigen die dynamischen, schluesselgenerierten, bedingten Daten, die echte Anwendungen tatsaechlich seeden, und sie scheitern laut, statt Ihre Migrationshistorie stillschweigend zu beschaedigen. Reservieren Sie HasData fuer den engen Fall, fuer den es umbenannt wurde: kleine, feste, deterministische Referenzdaten mit von Hand vergebenen Schluesseln, die Sie wirklich zusammen mit Ihrem Schema versionieren und diffen wollen.

Die zu vermeidende Falle ist der historische Standard. Jahrelang war HasData die einzige eingebaute Option, daher griffen Codebasen reflexartig danach und ertranken dann in Phantom-Migrationen und PendingModelChangesWarning. Wenn Sie in EF Core 11 neu beginnen, kehren Sie diesen Instinkt um: UseSeeding zuerst, HasData nur, wenn Sie benennen koennen, warum die Daten ins Modell gehoeren. Wenn Sie records-basierte Entitaeten pflegen, gilt dieselbe Aufteilung sauber, da Records mit EF Core 11 gut funktionieren unter beiden Mechanismen.

Verwandt

Quellen

Comments

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

< Zurück