Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
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:
-
Rozmieszczanie danych opcji konfiguracji (
UseSeeding) - Niestandardowa logika inicjowania
-
Dane zarządzane przez model (
HasData) - Ręczne dostosowywanie migracji
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 jest wywoływana z metody EnsureCreated, a UseAsyncSeeding jest wywoływana z metody EnsureCreatedAsync. W przypadku korzystania z tej funkcji zaleca się zaimplementowanie zarówno metody UseSeeding, jak i UseAsyncSeeding przy użyciu podobnej logiki, nawet jeśli kod oparty na EF jest asynchroniczny. Narzędzia EF Core obecnie opierają się na synchronicznej wersji metody i nie będą poprawnie inicjować bazy danych, jeśli metoda UseSeeding nie jest zaimplementowana.
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.
Wskazówka
Jeśli musisz zastosować migracje w ramach zautomatyzowanego wdrożenia, możesz utworzyć skrypt SQL, którego podgląd można wyświetlić 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ń zewnętrznego interfejsu API, takie jak role i tworzenie 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 }
});