Udostępnij za pośrednictwem


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 UseSeeding konfiguracji i UseAsyncSeeding metody

Wprowadzono UseSeeding program EF 9 i UseAsyncSeeding metody, które zapewniają wygodny sposób rozmieszczania bazy danych przy użyciu danych początkowych. 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 UseSeeding metody i UseAsyncSeeding jest chroniony przez mechanizm blokowania migracji, aby zapobiec problemom ze współbieżnością.

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

Napiwek

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 metoda jest wywoływana EnsureCreated z metody i UseAsyncSeeding jest wywoływana EnsureCreatedAsync z metody . W przypadku korzystania z tej funkcji zaleca się zaimplementowanie obu UseSeeding metod i UseAsyncSeeding przy użyciu podobnej logiki, nawet jeśli kod korzystający z programu EF jest asynchroniczny. Narzędzia EF Core obecnie opierają się na synchronicznej wersji metody i nie będą poprawnie rozmieszczać bazy danych, jeśli UseSeeding metoda nie jest zaimplementowana.

Niestandardowa logika inicjowania

Prostym i zaawansowanym sposobem wykonywania rozmieszczania danych jest użycie DbContext.SaveChangesAsync() przed rozpoczęciem wykonywania głównej logiki aplikacji. Zaleca się użycie UseSeeding metody i UseAsyncSeeding w tym celu, jednak czasami użycie tych metod nie jest dobrym rozwiązaniem. Przykładowy scenariusz polega na tym, że inicjowanie 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 publikowania.

Dane zarządzane przez model

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 spowoduje to skonfigurowanie zarządzanych danych dla elementu 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 należy jawnie skonfigurować jednostkę sprzężenia. Jeśli typ jednostki ma jakiekolwiek właściwości w stanie w tle (np. LanguageCountry poniższa jednostka sprzężenia), można użyć klasy anonimowej do podania 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.

Napiwek

Jeśli musisz zastosować migracje w ramach zautomatyzowanego wdrożenia, możesz utworzyć skrypt SQL, który można wyświetlić przed wykonaniem podglądu.

Alternatywnie możesz użyć context.Database.EnsureCreatedAsync() polecenia , aby utworzyć nową bazę danych zawierającą dane zarządzane, na przykład dla testowej bazy danych lub w przypadku korzystania z dostawcy w pamięci lub dowolnej nierelacyjnej bazy danych. 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 przy użyciu metody używanej HasData do nazywania się "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". UseSeeding metody i UseAsyncSeeding powinny być używane do rozmieszczania 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 zawiera dowolną z następujących metod, zalecane jest użycie UseSeeding metod opisanych UseAsyncSeeding w pierwszej sekcji:

  • Tymczasowe dane do testowania
  • Dane zależne od stanu bazy danych
  • Dane, które są duże (rozmieszczanie danych jest 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ń zewnętrznego interfejsu API, takich jak ASP.NET role tożsamości podstawowej i tworzenie użytkowników
  • Dane, które nie są stałe i deterministyczne, takie jak rozmieszczanie do elementu DateTime.Now.

Ręczne dostosowywanie migracji

Po dodaniu migracji zmiany w danych określonych za HasData pomocą są przekształcane w wywołania metody InsertData(), UpdateData()i DeleteData(). Jednym ze sposobów obejścia niektórych ograniczeń jest HasData 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 }
    });