Předvyplnění dat

Seedování dat je proces naplnění databáze počáteční sadou dat.

V EF Core můžete dosáhnout několika způsoby:

Možnosti UseSeeding a UseAsyncSeeding metody konfigurace

EF 9 zavedl a UseSeeding UseAsyncSeeding metody, které poskytují pohodlný způsob odsaování databáze s počátečními daty. Cílem těchto metod je zlepšit zkušenosti s používáním vlastní inicializační logiky (vysvětleno níže). Poskytují jedno jasné umístění, kde lze umístit veškerý kód počátečních dat. Kromě toho je kód uvnitř UseSeeding a UseAsyncSeeding metody chráněný mechanismem uzamčení migrace, aby se zabránilo problémům se souběžností.

Nové metody počátečního EnsureCreated nastavení se volají jako součást operace Migrate a dotnet ef database update příkazy, i když neexistují žádné změny modelu a nebyly použity žádné migrace.

Tip

Použití UseSeeding a UseAsyncSeeding je doporučeným způsobem počátečního nastavení databáze při práci s EF Core.

Tyto metody je možné nastavit v kroku konfigurace možností. Zde je příklad:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFDataSeeding;Trusted_Connection=True;ConnectRetryCount=0")
        .UseSeeding((context, _) =>
        {
            var testBlog = context.Set<Blog>().FirstOrDefault(b => b.Url == "http://test.com");
            if (testBlog == null)
            {
                context.Set<Blog>().Add(new Blog { Url = "http://test.com" });
                context.SaveChanges();
            }
        })
        .UseAsyncSeeding(async (context, _, cancellationToken) =>
        {
            var testBlog = await context.Set<Blog>().FirstOrDefaultAsync(b => b.Url == "http://test.com", cancellationToken);
            if (testBlog == null)
            {
                context.Set<Blog>().Add(new Blog { Url = "http://test.com" });
                await context.SaveChangesAsync(cancellationToken);
            }
        });

Poznámka:

UseSeeding je volána z EnsureCreated metody a UseAsyncSeeding je volána z EnsureCreatedAsync metody. Při použití této funkce se doporučuje implementovat obě UseSeeding metody UseAsyncSeeding pomocí podobné logiky, i když je kód používající EF asynchronní. Nástroje EF Core v současné době závisí na synchronní verzi metody a pokud není metoda implementována správně UseSeeding , databáze se nenasadí správně.

Logika vlastní inicializace

Jednoduchým a výkonným způsobem, jak provádět počáteční data, je použít DbContext.SaveChanges() před zahájením provádění hlavní logiky aplikace. Doporučuje se použít UseSeeding a UseAsyncSeeding pro tento účel, ale někdy použití těchto metod není dobrým řešením. Ukázkový scénář je, když počáteční vyžaduje použití dvou různých kontextů v jedné transakci. Níže je ukázka kódu, která provádí vlastní inicializaci přímo v aplikaci:

using (var context = new DataSeedingContext())
{
    context.Database.EnsureCreated();

    var testBlog = context.Blogs.FirstOrDefault(b => b.Url == "http://test.com");
    if (testBlog == null)
    {
        context.Blogs.Add(new Blog { Url = "http://test.com" });
        context.SaveChanges();
    }

}

Upozorňující

Počáteční kód by neměl být součástí normálního spuštění aplikace, protože to může způsobit problémy souběžnosti při spuštění více instancí a zároveň by vyžadovalo, aby aplikace měla oprávnění ke změně schématu databáze.

V závislosti na omezeních nasazení je možné inicializační kód spustit různými způsoby:

  • Místní spuštění inicializační aplikace
  • Nasazení inicializační aplikace s hlavní aplikací, vyvolání rutiny inicializace a zakázání nebo odebrání inicializační aplikace.

Obvykle se to dá automatizovat pomocí profilů publikování.

Modelem spravovaná data

Data lze také přidružit k typu entity v rámci konfigurace modelu. Migrace EF Core pak můžou automaticky vypočítat operace vložení, aktualizace nebo odstranění, které je potřeba použít při upgradu databáze na novou verzi modelu.

Upozorňující

Migrace zohledňuje změny modelu pouze při určování operace, která se má provést, aby se spravovaná data dostala do požadovaného stavu. Jakékoli změny dat provedených mimo migrace proto můžou být ztraceny nebo způsobit chybu.

Toto nastavení například nakonfiguruje spravovaná Country data pro :OnModelCreating

modelBuilder.Entity<Country>(b =>
{
    b.Property(x => x.Name).IsRequired();
    b.HasData(
        new Country { CountryId = 1, Name = "USA" },
        new Country { CountryId = 2, Name = "Canada" },
        new Country { CountryId = 3, Name = "Mexico" });
});

Pokud chcete přidat entity, které mají relaci, je potřeba zadat hodnoty cizího klíče:

modelBuilder.Entity<City>().HasData(
    new City { Id = 1, Name = "Seattle", LocatedInId = 1 },
    new City { Id = 2, Name = "Vancouver", LocatedInId = 2 },
    new City { Id = 3, Name = "Mexico City", LocatedInId = 3 },
    new City { Id = 4, Name = "Puebla", LocatedInId = 3 });

Při správě dat pro navigace M:N je potřeba entitu spojení nakonfigurovat explicitně. Pokud má typ entity nějaké vlastnosti ve stínovém stavu (např. entitu LanguageCountry join níže), dá se k zadání hodnot použít anonymní třída:

modelBuilder.Entity<Language>(b =>
{
    b.HasData(
        new Language { Id = 1, Name = "English" },
        new Language { Id = 2, Name = "French" },
        new Language { Id = 3, Name = "Spanish" });

    b.HasMany(x => x.UsedIn)
        .WithMany(x => x.OfficialLanguages)
        .UsingEntity(
            "LanguageCountry",
            r => r.HasOne(typeof(Country)).WithMany().HasForeignKey("CountryId").HasPrincipalKey(nameof(Country.CountryId)),
            l => l.HasOne(typeof(Language)).WithMany().HasForeignKey("LanguageId").HasPrincipalKey(nameof(Language.Id)),
            je =>
            {
                je.HasKey("LanguageId", "CountryId");
                je.HasData(
                    new { LanguageId = 1, CountryId = 2 },
                    new { LanguageId = 2, CountryId = 2 },
                    new { LanguageId = 3, CountryId = 3 });
            });
});

Typy vlastněných entit lze konfigurovat podobným způsobem:

modelBuilder.Entity<Language>().OwnsOne(p => p.Details).HasData(
    new { LanguageId = 1, Phonetic = false, Tonal = false, PhonemesCount = 44 },
    new { LanguageId = 2, Phonetic = false, Tonal = false, PhonemesCount = 36 },
    new { LanguageId = 3, Phonetic = true, Tonal = false, PhonemesCount = 24 });

Další kontext najdete v úplném ukázkovém projektu .

Po přidání dat do modelu by se migrace měly použít k použití změn.

Tip

Pokud potřebujete použít migrace jako součást automatizovaného nasazení, můžete vytvořit skript SQL, který se dá před spuštěním zobrazit ve verzi Preview.

Alternativně můžete vytvořit context.Database.EnsureCreated() novou databázi obsahující spravovaná data, například pro testovací databázi nebo při použití zprostředkovatele v paměti nebo jakékoli nerelační databáze. Upozorňujeme, že pokud databáze již existuje, EnsureCreated() neaktualizuje schéma ani spravovaná data v databázi. U relačních databází byste neměli volat EnsureCreated() , pokud plánujete používat migrace.

Poznámka:

Naplnění databáze metodou HasData , která se označuje jako "počáteční data". Toto pojmenování nastavuje nesprávná očekávání, protože funkce má řadu omezení a je vhodná pouze pro konkrétní typy dat. Proto jsme se rozhodli přejmenovat data spravovaná modelem. UseSeeding a UseAsyncSeeding metody by měly být použity pro počáteční data pro obecné účely.

Omezení modelem spravovaných dat

Tento typ dat spravuje migrace a skript pro aktualizaci dat, která jsou již v databázi, je potřeba vygenerovat bez připojení k databázi. To platí pro některá omezení:

  • Hodnotu primárního klíče je potřeba zadat, i když je obvykle vygenerovaná databází. Použije se k detekci změn dat mezi migracemi.
  • Dříve vložená data budou odebrána, pokud se primární klíč změní jakýmkoli způsobem.

Tato funkce je proto nejužitečnější pro statická data, u která se neočekává změna mimo migrace a nezávisí na ničem jiném v databázi, například PSČ.

Pokud váš scénář obsahuje některou z následujících možností, doporučujeme použít UseSeeding a UseAsyncSeeding použít metody popsané v první části:

  • Dočasná data pro testování
  • Data, která závisí na stavu databáze
  • Data, která jsou velká (počáteční data se zaznamenávají ve snímcích migrace a velké objemy dat můžou rychle vést k obrovským souborům a snížení výkonu).
  • Data, která potřebují, aby databáze vygenerovala hodnoty klíčů, včetně entit, které jako identitu používají alternativní klíče
  • Data, která vyžadují vlastní transformaci (která se nezpracují převody hodnot), například některá hashování hesel
  • Data, která vyžadují volání externího rozhraní API, jako jsou role základní identity ASP.NET a vytváření uživatelů

Přizpůsobení ruční migrace

Při přidání migrace se změny zadaných dat HasData transformují na volání , InsertData()UpdateData()a DeleteData(). Jedním ze způsobů, jak obejít některá omezení HasData , je ruční přidání těchto volání nebo vlastních operací do migrace.

migrationBuilder.InsertData(
    table: "Countries",
    columns: new[] { "CountryId", "Name" },
    values: new object[,]
    {
        { 1, "USA" },
        { 2, "Canada" },
        { 3, "Mexico" }
    });

migrationBuilder.InsertData(
    table: "Languages",
    columns: new[] { "Id", "Name", "Details_PhonemesCount", "Details_Phonetic", "Details_Tonal" },
    values: new object[,]
    {
        { 1, "English", 44, false, false },
        { 2, "French", 36, false, false },
        { 3, "Spanish", 24, true, false }
    });

migrationBuilder.InsertData(
    table: "Cites",
    columns: new[] { "Id", "LocatedInId", "Name" },
    values: new object[,]
    {
        { 1, 1, "Seattle" },
        { 2, 2, "Vancouver" },
        { 3, 3, "Mexico City" },
        { 4, 3, "Puebla" }
    });

migrationBuilder.InsertData(
    table: "LanguageCountry",
    columns: new[] { "CountryId", "LanguageId" },
    values: new object[,]
    {
        { 2, 1 },
        { 2, 2 },
        { 3, 3 }
    });