Adatelőkészítés
Az adatbevetés egy adatbázis kezdeti adatkészlettel való feltöltésének folyamata.
Ez az EF Core-ban többféleképpen is elvégezhető:
-
Konfigurációs beállítások – adatbevetés (
UseSeeding
) - egyéni inicializálási logika
-
modell által felügyelt adatok (
HasData
) - manuális migrálás testreszabása
Konfigurációs beállítások UseSeeding
és UseAsyncSeeding
metódusok
Az EF 9 bevezette UseSeeding
és UseAsyncSeeding
módszereket, amelyek kényelmes módot biztosítanak az adatbázis kezdeti adatokkal való bevetésére. Ezek a módszerek az egyéni inicializálási logika használatának élményét hivatottak javítani (az alábbiakban ismertetjük). Egy egyértelmű helyet biztosítanak, ahol az összes adatbevetési kód elhelyezhető. Ezenkívül a UseSeeding
és UseAsyncSeeding
metódusok kódját a migrálási zárolási mechanizmus védi, az egyidejűségi problémák megelőzése érdekében.
Az új vetési módszereket EnsureCreated
művelet, Migrate
és dotnet ef database update
parancs részeként hívjuk meg, még akkor is, ha nincsenek modellmódosítások, és nem alkalmaztak áttelepítést.
Borravaló
A UseSeeding
és a UseAsyncSeeding
használata az adatbázis kezdeti adatokkal való üzembe helyezésének ajánlott módja az EF Core használatakor.
Ezeket a metódusokat a beállítási lehetőségekkonfigurációs lépésében lehet beállítani. Íme egy példa:
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);
}
});
Jegyzet
A UseSeeding
-t a EnsureCreated
metódusból hívják meg, és a UseAsyncSeeding
-t a EnsureCreatedAsync
metódusból hívják meg. A funkció használatakor ajánlott UseSeeding
és UseAsyncSeeding
metódusokat is hasonló logikával implementálni, még akkor is, ha az EF-t használó kód aszinkron. Az EF Core eszközkészlet jelenleg a módszer szinkron verziójára támaszkodik, és nem fogja megfelelően kiépítani az adatbázist, ha a UseSeeding
metódus nincs implementálva.
Egyéni inicializálási logika
Az adatekjesítés végrehajtásának egyszerű és hatékony módja, ha a DbContext.SaveChangesAsync()
-t használja, mielőtt a fő alkalmazáslogika végrehajtása elkezdődik. Erre a célra ajánlott UseSeeding
és UseAsyncSeeding
használni, de néha ezek a módszerek nem jó megoldás. Példaforgatókönyv, ha a vetéshez két különböző kontextust kell használni egy tranzakcióban. Az alábbiakban egy kódminta látható, amely közvetlenül egyéni inicializálást hajt végre az alkalmazásban:
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();
}
}
Figyelmeztetés
Az inicializáló kód nem lehet része a normál alkalmazásvégrehajtásnak, mivel ez egyidejűségi problémákat okozhat, ha több példány fut, és ehhez az alkalmazásnak jogosultsággal kell rendelkeznie az adatbázisséma módosításához.
Az üzembe helyezés korlátaitól függően az inicializálási kód többféleképpen is végrehajtható:
- Az inicializálási alkalmazás helyi futtatása
- Az inicializálási alkalmazás központi telepítése a főalkalmazással, az inicializálási rutin meghívása, az inicializálási alkalmazás letiltása vagy eltávolítása.
Ez általában közzétételi profilokhasználatával automatizálható.
Felügyelt adatok modellezése
Az adatok entitástípushoz is társíthatók a modellkonfiguráció részeként. Ezután az EF Core migrálások automatikusan kiszámítják, hogy milyen beszúrási, frissítési vagy törlési műveleteket kell alkalmazni az adatbázis új verziójára való frissítéskor.
Figyelmeztetés
A migrálások csak a modell módosításait veszi figyelembe annak meghatározásakor, hogy milyen műveletet kell végrehajtani a felügyelt adatok kívánt állapotba helyezéséhez. Így a migráláson kívül végrehajtott adatok bármilyen módosítása elveszhet, vagy hibát okozhat.
Például ez konfigurálja a felügyelet alá vont adatokat egy Country
számára 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" });
});
Ha olyan entitásokat szeretne hozzáadni, amelyek kapcsolatban vannak, meg kell adni az idegenkulcs-értékeket:
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 });
A több-a-többhöz navigációk adatainak kezelésekor az illesztési entitást explicit módon kell konfigurálni. Ha az entitástípus rendelkezik rejtett állapotú tulajdonságokkal (pl. az alábbi LanguageCountry
kapcsoló entitás esetében), névtelen osztállyal lehet értékeket adni.
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 });
});
});
A saját entitástípusok hasonló módon konfigurálhatók:
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 });
További információért tekintse meg a teljes mintaprojekt.
Miután az adatokat hozzáadta a modellhez, a módosítások alkalmazásához migrációkat kell használni.
Borravaló
Ha egy automatizált üzembe helyezés részeként migrálásokat kell alkalmaznia, létrehozhat egy SQL-szkriptet, amely a végrehajtás előtt megtekinthető.
Alternatívaként a(z) context.Database.EnsureCreatedAsync()
segítségével létrehozhat egy új adatbázist, amely kezelt adatokat tartalmaz, például egy tesztadatbázis számára, vagy a memóriabeli tároló, illetve bármely nem relációs adatbázis használatakor. Vegye figyelembe, hogy ha az adatbázis már létezik, EnsureCreatedAsync()
nem frissíti a sémát és a felügyelt adatokat az adatbázisban. Relációs adatbázisok esetén nem szabad meghívni EnsureCreatedAsync()
, ha migrálást tervez használni.
Jegyzet
Az adatbázis feltöltése az "adatbevetésnek" nevezett HasData
módszerrel. Ez az elnevezés helytelen elvárásokat állít be, mivel a funkció számos korlátozással rendelkezik, és csak bizonyos adattípusokhoz megfelelő. Ezért döntöttünk úgy, hogy átnevezzük a "modell által felügyelt adatokra".
UseSeeding
és UseAsyncSeeding
módszereket kell használni az általános célú adatbevetéshez.
A modell által felügyelt adatok korlátozásai
Ezt az adattípust migrálások kezelik, és az adatbázisban már meglévő adatok frissítéséhez szükséges szkriptet az adatbázishoz való csatlakozás nélkül kell létrehozni. Ez bizonyos korlátozásokat vezet be:
- Az elsődleges kulcs értékét akkor is meg kell adni, ha azt általában az adatbázis hozza létre. A rendszer a migrálások közötti adatváltozások észlelésére szolgál.
- A korábban beszúrt adatok törlődnek, ha az elsődleges kulcs bármilyen módon módosul.
Ezért ez a funkció leginkább olyan statikus adatok esetében hasznos, amelyek várhatóan nem változnak a migráláson kívül, és nem függ az adatbázisban lévő bármi mástól, például az irányítószámoktól.
Ha a forgatókönyv az alábbiak bármelyikét tartalmazza, ajánlott az első szakaszban leírt UseSeeding
és UseAsyncSeeding
metódusokat használni:
- Ideiglenes adatok teszteléshez
- Az adatbázis állapotától függő adatok
- Nagy méretű adatok (a bevetési adatok a migrációs pillanatképekben kerülnek rögzítésre, és a nagy adatmennyiség gyorsan hatalmas fájlokhoz és teljesítményromláshoz vezethet).
- Az adatbázis által generálandó kulcsértékeket igénylő adatok, beleértve azokat az entitásokat is, amelyek alternatív kulcsokat használnak identitásként
- Egyéni átalakítást igénylő adatok (amelyeket nem értékkonvertálásokkezelnek), például jelszókivonatok
- Külső API-ra irányuló hívásokat igénylő adatok, például ASP.NET Core Identity-szerepkörök és felhasználók létrehozása
- Nem rögzített és determinisztikusan nem meghatározott adatok, például a
DateTime.Now
iniciálása.
Manuális migrálás testreszabása
Migrálás hozzáadásakor a HasData
-n megadott adatok változásai InsertData()
, UpdateData()
és DeleteData()
hívássá alakulnak át. A HasData
néhány korlátozásának megkerülésére az egyik módszer az, ha manuálisan hozzáadja ezeket a hívásokat, vagy egyéni műveleteket, a migráláshoz.
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 }
});