Gegevensinitialisatie
Gegevens seeding is het proces van het vullen van een database met een eerste set gegevens.
Er zijn verschillende manieren waarop dit kan worden bereikt in EF Core:
-
Configuratieopties voor gegevenszaaien (
UseSeeding
) - aangepaste initialisatielogica
-
model voor beheerde gegevens (
HasData
) - handmatige migratieaanpassing op maat
Configuratieopties UseSeeding
en UseAsyncSeeding
methoden
EF 9 heeft UseSeeding
en UseAsyncSeeding
methoden geïntroduceerd, die een handige manier bieden om de database te seeden met initiële gegevens. Deze methoden zijn bedoeld om de ervaring van het gebruik van aangepaste initialisatielogica te verbeteren (zie hieronder). Ze bieden één duidelijke locatie waar alle gegevensinvoercode kan worden geplaatst. Bovendien wordt de code binnen UseSeeding
en UseAsyncSeeding
methoden beveiligd door het mechanisme voor migratievergrendeling om gelijktijdigheidsproblemen te voorkomen.
De nieuwe seeding-methoden worden aangeroepen als onderdeel van EnsureCreated
bewerking, Migrate
en dotnet ef database update
opdracht, zelfs als er geen modelwijzigingen zijn en er geen migraties zijn toegepast.
Fooi
Het gebruik van UseSeeding
en UseAsyncSeeding
is de aanbevolen manier om de database te seeden met initiële gegevens bij het werken met EF Core.
Deze methoden kunnen worden ingesteld in de configuratiestap opties. Hier volgt een voorbeeld:
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);
}
});
Notitie
UseSeeding
wordt aangeroepen vanuit de methode EnsureCreated
en UseAsyncSeeding
wordt aangeroepen vanuit de EnsureCreatedAsync
methode. Wanneer u deze functie gebruikt, is het raadzaam om methoden voor zowel UseSeeding
als UseAsyncSeeding
te implementeren met behulp van vergelijkbare logica, zelfs als de code met EF asynchroon is. EF Core-hulpprogramma's zijn momenteel afhankelijk van de synchrone versie van de methode en seeden de database niet correct als de methode UseSeeding
niet is geïmplementeerd.
Aangepaste initialisatielogica
Een eenvoudige en krachtige manier om gegevens seeding uit te voeren, is door DbContext.SaveChangesAsync()
te gebruiken voordat de hoofdtoepassingslogica begint met de uitvoering. Het wordt aanbevolen om hiervoor UseSeeding
en UseAsyncSeeding
te gebruiken, maar soms is het gebruik van deze methoden geen goede oplossing. Een voorbeeldscenario is wanneer seeding twee verschillende contexten in één transactie vereist. Hieronder ziet u een codevoorbeeld voor het rechtstreeks uitvoeren van aangepaste initialisatie in de toepassing:
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();
}
}
Waarschuwing
De seeding-code mag geen deel uitmaken van de normale uitvoering van de app, omdat dit gelijktijdigheidsproblemen kan veroorzaken wanneer meerdere exemplaren worden uitgevoerd en de app ook toestemming nodig heeft om het databaseschema te wijzigen.
Afhankelijk van de beperkingen van uw implementatie kan de initialisatiecode op verschillende manieren worden uitgevoerd:
- De initialisatie-app lokaal uitvoeren
- De initialisatie-app implementeren met de hoofd-app, de initialisatieroutine aanroepen en de initialisatie-app uitschakelen of verwijderen.
Dit kan meestal worden geautomatiseerd met behulp van publicatieprofielen.
Beheerde gegevens modelleren
Gegevens kunnen ook worden gekoppeld aan een entiteitstype als onderdeel van de modelconfiguratie. Vervolgens kunnen EF Core migraties automatisch berekenen welke invoeg-, update- of verwijderbewerkingen moeten worden toegepast bij het bijwerken van de database naar een nieuwe versie van het model.
Waarschuwing
Migraties zijn alleen van toepassing op modelwijzigingen bij het bepalen welke bewerking moet worden uitgevoerd om de beheerde gegevens in de gewenste status te krijgen. Zo kunnen wijzigingen in de gegevens die buiten migraties worden uitgevoerd verloren gaan of een fout veroorzaken.
Als voorbeeld configureert u beheerde gegevens voor een Country
in 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" });
});
Als u entiteiten wilt toevoegen die in de context van een database een relatie hebben, moeten de waarden van de foreign key worden opgegeven:
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 });
Wanneer u gegevens voor veel-op-veel-navigatie beheert, moet de join-entiteit expliciet worden geconfigureerd. Als het entiteitstype eigenschappen heeft met de schaduwstatus (bijvoorbeeld de onderstaande LanguageCountry
join-entiteit), kan een anonieme klasse worden gebruikt om de waarden op te geven:
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 });
});
});
Entiteitstypen in eigendom kunnen op vergelijkbare wijze worden geconfigureerd:
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 });
Zie het volledige voorbeeldproject voor meer context.
Zodra de gegevens aan het model zijn toegevoegd, moeten migraties worden gebruikt om de wijzigingen toe te passen.
Tip
Als u migraties wilt toepassen als onderdeel van een geautomatiseerde implementatie, kunt u een SQL-script maken dat kan worden bekeken vóór de uitvoering.
U kunt ook context.Database.EnsureCreatedAsync()
gebruiken om een nieuwe database te maken die de beheerde gegevens bevat, bijvoorbeeld voor een testdatabase of wanneer u de provider in het geheugen of een niet-relationele database gebruikt. Houd er rekening mee dat als de database al bestaat, EnsureCreatedAsync()
het schema of de beheerde gegevens in de database niet bijwerkt. Voor relationele databases moet u EnsureCreatedAsync()
niet aanroepen als u migraties wilt gebruiken.
Notitie
De methode HasData
die vroeger werd gebruikt om de database te vullen, stond bekend als data seeding. Met deze naamgeving worden onjuiste verwachtingen ingesteld, omdat de functie een aantal beperkingen heeft en alleen geschikt is voor specifieke typen gegevens. Daarom hebben we besloten om de naam ervan te wijzigen in 'beheerde gegevens modelleren'.
UseSeeding
en UseAsyncSeeding
methoden moeten worden gebruikt voor het seeden van gegevens voor algemeen gebruik.
Beperkingen van beheerde modelgegevens
Dit type gegevens wordt beheerd door migraties en het script voor het bijwerken van de gegevens die zich al in de database bevinden, moeten worden gegenereerd zonder verbinding te maken met de database. Dit brengt enkele beperkingen met zich mee:
- De waarde van de primaire sleutel moet worden opgegeven, zelfs als deze meestal wordt gegenereerd door de database. Deze wordt gebruikt om gegevenswijzigingen tussen migraties te detecteren.
- Eerder ingevoegde gegevens worden verwijderd als de primaire sleutel op welke manier dan ook wordt gewijzigd.
Daarom is deze functie het handigst voor statische gegevens die niet buiten migraties worden gewijzigd en die niet afhankelijk zijn van iets anders in de database, bijvoorbeeld postcodes.
Als uw scenario een van de volgende methoden bevat, wordt u aangeraden UseSeeding
en UseAsyncSeeding
methoden te gebruiken die in de eerste sectie worden beschreven:
- Tijdelijke gegevens voor testen
- Gegevens die afhankelijk zijn van de databasestatus
- Gegevens die groot zijn (seeding-gegevens worden vastgelegd in migratiemomentopnamen en grote gegevens kunnen snel leiden tot enorme bestanden en verminderde prestaties).
- Gegevens waarvoor sleutelwaarden moeten worden gegenereerd door de database, inclusief entiteiten die alternatieve sleutels gebruiken als identiteit
- Gegevens waarvoor aangepaste transformatie is vereist (die niet wordt verwerkt door waardeconversies), zoals wachtwoordhashing
- Gegevens die aanroepen naar externe API vereisen, zoals ASP.NET Core Identity-rollen en het maken van gebruikers
- Gegevens die niet vast en deterministisch zijn, zoals initialiseren voor
DateTime.Now
.
Aanpassing van handmatige migratie
Wanneer een migratie wordt toegevoegd, worden de wijzigingen aan de opgegeven gegevens met HasData
omgezet in aanroepen naar InsertData()
, UpdateData()
en DeleteData()
. Een manier om bepaalde beperkingen van HasData
te omzeilen, is door deze aanroepen handmatig toe te voegen of aangepaste bewerkingen toe te voegen aan de migratie.
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 }
});