Delen via


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