Wstępne wypełnianie danych

Zasiewanie danych to proces wypełniania bazy danych początkowym zestawem danych.

Istnieje kilka sposobów, na które można to zrobić w programie EF Core:

Opcje konfiguracji UseSeeding i metody UseAsyncSeeding

W EF 9 wprowadzono metody UseSeeding i UseAsyncSeeding, które zapewniają wygodny sposób wypełniania bazy danych danymi początkowymi. Te metody mają na celu ulepszenie środowiska korzystania z niestandardowej logiki inicjowania (wyjaśniono poniżej). Zapewniają jedną wyraźną lokalizację, w której można umieścić cały kod rozmieszczania danych. Ponadto kod wewnątrz metod UseSeeding i UseAsyncSeeding jest chroniony przez mechanizm blokowania migracji, aby zapobiec problemom ze współbieżnością.

Nowe metody rozmieszczania są wywoływane w ramach operacji EnsureCreated oraz polecenia Migrate i dotnet ef database update, nawet jeśli nie ma żadnych zmian modelu i nie zastosowano żadnych migracji.

Wskazówka

Użycie UseSeeding i UseAsyncSeeding jest zalecanym sposobem rozmieszczania bazy danych z danymi początkowymi podczas pracy z programem EF Core.

Te metody można skonfigurować w kroku konfiguracji opcji. Oto przykład:

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

Uwaga

UseSeeding / UseAsyncSeeding są wywoływane podczas EnsureCreated/EnsureCreatedAsync migracji i po ich zastosowaniu (na przykład Migrate/MigrateAsyncpakiety , dotnet ef database updatei migracji). Narzędzia i pakiety platformy EF Core korzystają obecnie z synchronicznego delegata, więc zawsze implementuj UseSeeding , nawet jeśli aplikacja zwykle używa asynchronicznych interfejsów API.

Zachowanie wdrożenia

UseSeeding i UseAsyncSeeding uruchamiać tylko wtedy, gdy program EF Core wykonuje operację inicjowania lub migracji bazy danych. Wybierz odpowiednio mechanizm wdrażania:

Operation Wywoływany delegat rozmieszczania
EnsureCreated lub Migrate UseSeeding
EnsureCreatedAsync lub MigrateAsync UseAsyncSeeding
dotnet ef database update lub Update-Database UseSeeding
Pakiet migracji UseSeeding
Skrypt SQL wykonywany przez zewnętrzne narzędzie SQL Żadne

W przypadku wdrożenia automatycznego, które musi uruchomić UseSeedingprogram , należy użyć pakietu migracji lub dedykowanego procesu inicjowania. Narzędzia i pakiety platformy EF Core wywołują delegata synchronicznego, więc zawsze implementuj UseSeeding , nawet jeśli aplikacja zwykle używa asynchronicznych interfejsów API. Użyj skryptu SQL, gdy wymagane jest przeglądanie lub wykonywanie bazy danych bazy danych, a dane inicjowane są reprezentowane przez operacje migracji. Zobacz Applying Migrations for the tradeoffs (Stosowanie migracji dla kompromisów).

Rozmieszczanie jest również uruchamiane po obniżeniu poziomu migracji. Jeśli aplikacja obsługuje obniżanie poziomu migracji, która nie zawiera każdej tabeli używanej przez kod inicjujący, przed wykonaniem zapytania sprawdź, czy wymagany schemat istnieje. Jest to szczególnie ważne w przypadku przywracania wszystkich migracji przez element docelowy 0.

Aplikacje Aspirujące mogą koordynować lokalne wykonywanie migracji i publikować pakiety migracji lub skrypty za pomocą integracji migracji programu Aspire EF Core.

Niestandardowa logika inicjowania

Prostym i zaawansowanym sposobem wykonywania rozmieszczania danych jest użycie SaveChangesAsync przed rozpoczęciem wykonywania głównej logiki aplikacji. Zaleca się użycie UseSeeding i UseAsyncSeeding w tym celu, jednak czasami stosowanie tych metod nie jest dobrym rozwiązaniem. Przykładowy scenariusz polega na tym, że sianie wymaga użycia dwóch różnych kontekstów w jednej transakcji. Poniżej znajduje się przykład kodu wykonujący niestandardowe inicjowanie bezpośrednio w aplikacji:

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

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

Ostrzeżenie

Kod inicjujący nie powinien być częścią normalnego wykonywania aplikacji, ponieważ może to spowodować problemy ze współbieżnością, gdy uruchomiono wiele wystąpień i wymagałoby również, aby aplikacja miała uprawnienia do modyfikowania schematu bazy danych.

W zależności od ograniczeń wdrożenia kod inicjowania może być wykonywany na różne sposoby:

  • Lokalne uruchamianie aplikacji inicjalizacji
  • Wdrażanie aplikacji inicjowania przy użyciu głównej aplikacji, wywoływanie procedury inicjowania i wyłączanie lub usuwanie aplikacji inicjowania.

Zazwyczaj można to zautomatyzować przy użyciu profilów publikacji.

Dane zarządzane modelowo

Dane mogą być również skojarzone z typem jednostki w ramach konfiguracji modelu. Następnie migracje platformy EF Core mogą automatycznie obliczać operacje wstawiania, aktualizowania lub usuwania, które należy zastosować podczas uaktualniania bazy danych do nowej wersji modelu.

Ostrzeżenie

Migracje uwzględniają tylko zmiany modelu podczas określania, jaką operację należy wykonać w celu uzyskania zarządzanych danych do żądanego stanu. W związku z tym wszelkie zmiany danych wykonywane poza migracjami mogą zostać utracone lub spowodować błąd.

Na przykład, skonfiguruje to zarządzane dane dla komponentu Country w OnModelCreatingprogramie:

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" });
});

Aby dodać jednostki, które mają relację, należy określić wartości klucza obcego:

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

Podczas zarządzania danymi dla nawigacji wiele-do-wielu, jednostka łącząca musi być skonfigurowana jawnie. Jeśli typ jednostki ma jakiekolwiek właściwości w stanie cieniowym (np. poniższej jednostki sprzężenia LanguageCountry), można użyć klasy anonimowej do przekazania wartości.

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 jednostek należących do użytkownika można skonfigurować w podobny sposób:

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

Zobacz pełny przykładowy projekt, aby uzyskać więcej kontekstu.

Po dodaniu danych do modelu należy użyć migracji w celu zastosowania zmian.

HasData zmiany są konwertowane na InsertDataoperacje , UpdateDatai DeleteData , gdy migracja jest szkieletowa. Wywołanie Migrate nie sprawdza niezależnie bieżącej HasData konfiguracji. Po zmianie danych zarządzanych przez model dodaj i wdróż nową migrację.

Wskazówka

W przypadku wdrażania automatycznego użyj pakietu migracji. Użyj skryptu SQL , jeśli musi być wyświetlany podgląd lub zmieniany przed wykonaniem.

Alternatywnie możesz użyć EnsureCreatedAsync do utworzenia nowej bazy danych zawierającej dane zarządzane, na przykład testowej bazy danych, lub przy korzystaniu z dostawcy in-memory albo dowolnej bazy danych nierelacyjnej. Należy pamiętać, że jeśli baza danych już istnieje, EnsureCreatedAsync nie zaktualizuje schematu ani danych zarządzanych w bazie danych. W przypadku relacyjnych baz danych nie należy wywoływać EnsureCreatedAsync , jeśli planujesz używać migracji.

Uwaga

Wypełnianie bazy danych metodą HasData było kiedyś nazywane "rozmieszczaniem danych". To nazewnictwo określa nieprawidłowe oczekiwania, ponieważ funkcja ma wiele ograniczeń i jest odpowiednia tylko dla określonych typów danych. Dlatego postanowiliśmy zmienić jego nazwę na "dane zarządzane przez model". Metody UseSeeding i UseAsyncSeeding powinny być używane do zasiewania danych ogólnego przeznaczenia.

Ograniczenia danych zarządzanych przez model

Ten typ danych jest zarządzany przez migracje i skrypt w celu zaktualizowania danych, które są już w bazie danych, należy wygenerować bez nawiązywania połączenia z bazą danych. Nakłada to pewne ograniczenia:

  • Wartość klucza podstawowego musi być określona, nawet jeśli jest ona zwykle generowana przez bazę danych. Będzie on używany do wykrywania zmian danych między migracjami.
  • Wcześniej wstawione dane zostaną usunięte, jeśli klucz podstawowy zostanie zmieniony w jakikolwiek sposób.

W związku z tym ta funkcja jest najbardziej przydatna w przypadku danych statycznych, które nie powinny zmieniać się poza migracjami i nie zależą od niczego innego w bazie danych, na przykład kodów POCZTOWYCH.

Jeśli Twój scenariusz obejmuje dowolny z poniższych elementów, zaleca się wykorzystanie metod UseSeeding i UseAsyncSeeding, opisanych w pierwszej sekcji.

  • Tymczasowe dane do testowania
  • Dane zależne od stanu bazy danych
  • pl-PL: Dane, które są duże (dane początkowe są przechwytywane w migawkach migracji, a duże dane mogą szybko prowadzić do ogromnych plików i obniżonej wydajności).
  • Dane, które wymagają wygenerowania przez bazę danych wartości kluczy, w tym jednostek używających kluczy alternatywnych jako tożsamości
  • Dane wymagające przekształcenia niestandardowego (które nie są obsługiwane przez konwersje wartości), takie jak skróty haseł
  • Dane wymagające wywołań do zewnętrznego interfejsu API, takie jak tworzenie ról i użytkowników w ASP.NET Core Identity
  • Dane, które nie są stałe i deterministyczne, takie jak inicjowanie do DateTime.Now.

Ręczne dostosowywanie migracji

Po dodaniu migracji zmiany w danych, określonych za pomocą HasData, są przekształcane w wywołania do InsertData(), UpdateData() i DeleteData(). Jednym ze sposobów obejścia niektórych ograniczeń HasData jest ręczne dodanie tych wywołań lub operacji niestandardowych do migracji.

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

Te operacje są odpowiednie, gdy wartości i klucze są stałe podczas zapisywania migracji. Nie odpytują bieżącego stanu bazy danych. Zobacz Operacje na danych w migracjach, aby zapoznać się z InsertDataprzykładami przekształceń SQL specyficznych dla dostawcy , UpdateDatai DeleteData.