Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Penyemaian data adalah proses mengisi database dengan sekumpulan data awal.
Ada beberapa cara yang dapat dicapai dalam EF Core:
-
Penyemaian data opsi konfigurasi (
UseSeeding) - Logika inisialisasi kustom
-
Data yang dikelola model (
HasData) - Kustomisasi migrasi manual
Opsi konfigurasi UseSeeding dan metode UseAsyncSeeding
EF 9 diperkenalkan UseSeeding dan UseAsyncSeeding metode, yang menyediakan cara mudah untuk menyemai database dengan data awal. Metode ini bertujuan untuk meningkatkan pengalaman menggunakan logika inisialisasi kustom (dijelaskan di bawah). Mereka menyediakan satu lokasi yang jelas di mana semua kode penyemaian data dapat ditempatkan. Selain itu, metode UseSeeding dan UseAsyncSeeding dilindungi oleh mekanisme penguncian migrasi untuk mencegah masalah konkurensi.
Metode penyemaian baru digunakan sebagai bagian dari operasi EnsureCreated, Migrate, dan dotnet ef database update perintah, bahkan jika tidak ada perubahan model dan tidak ada migrasi yang diterapkan.
Petunjuk / Saran
Menggunakan UseSeeding dan UseAsyncSeeding merupakan cara yang direkomendasikan untuk menyemai database dengan data awal saat bekerja dengan EF Core.
Metode ini dapat disiapkan dalam langkah konfigurasi opsi. Berikut adalah contoh:
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);
}
});
Catatan
UseSeeding dipanggil dari EnsureCreated metode , dan UseAsyncSeeding dipanggil dari EnsureCreatedAsync metode . Saat menggunakan fitur ini, disarankan untuk mengimplementasikan metode UseSeeding dan UseAsyncSeeding menggunakan logika serupa, bahkan jika kode yang menggunakan EF tidak sinkron. Tooling EF Core saat ini bergantung pada versi sinkron dari metode tersebut dan tidak akan mengisi database dengan benar jika metode UseSeeding tidak diimplementasikan.
Logika inisialisasi kustom
Cara mudah dan kuat untuk melakukan penyemaian data adalah dengan menggunakan SaveChangesAsync sebelum logika aplikasi utama memulai eksekusi. Disarankan untuk menggunakan UseSeeding dan UseAsyncSeeding untuk tujuan itu, namun terkadang menggunakan metode ini bukanlah solusi yang baik. Contoh skenarionya adalah ketika penyemaian memerlukan penggunaan dua konteks yang berbeda dalam satu transaksi. Di bawah ini adalah sampel kode yang melakukan inisialisasi kustom dalam aplikasi secara langsung:
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();
}
}
Peringatan
Kode penyemaian tidak boleh menjadi bagian dari eksekusi aplikasi normal karena ini dapat menyebabkan masalah konkurensi saat beberapa instans berjalan dan juga akan mengharuskan aplikasi memiliki izin untuk mengubah skema database.
Bergantung pada batasan penyebaran Anda, kode inisialisasi dapat dijalankan dengan cara yang berbeda:
- Menjalankan aplikasi inisialisasi secara lokal
- Menyebarkan aplikasi inisialisasi dengan aplikasi utama, memanggil rutinitas inisialisasi dan menonaktifkan atau menghapus aplikasi inisialisasi.
Ini biasanya dapat diotomatisasi dengan menggunakan profil penerbitan.
Data yang dikelola oleh model
Data juga dapat dikaitkan dengan jenis entitas sebagai bagian dari konfigurasi model. Kemudian, migrasi EF Core dapat secara otomatis menghitung operasi penyisipan, pembaruan, atau penghapusan apa yang perlu diterapkan saat meningkatkan database ke versi model baru.
Peringatan
Migrasi hanya mempertimbangkan perubahan model saat menentukan operasi apa yang harus dilakukan untuk memasukkan data terkelola ke dalam status yang diinginkan. Dengan demikian setiap perubahan pada data yang dilakukan di luar migrasi mungkin hilang atau menyebabkan kesalahan.
Sebagai contoh, ini akan mengonfigurasi data terkelola untuk Country di 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" });
});
Untuk menambahkan entitas yang memiliki hubungan, nilai kunci asing perlu ditentukan:
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 });
Saat mengelola data untuk navigasi banyak ke banyak, entitas gabungan perlu dikonfigurasi secara eksplisit. Jika jenis entitas memiliki properti apa pun dalam status bayangan (misalnya LanguageCountry entitas gabungan di bawah), kelas anonim dapat digunakan untuk memberikan nilai:
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 });
});
});
Jenis entitas yang dimiliki dapat dikonfigurasi dengan cara yang sama:
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 });
Lihat proyek sampel lengkap untuk konteks selengkapnya.
Setelah data ditambahkan ke model, migrasi harus digunakan untuk menerapkan perubahan.
Petunjuk / Saran
Jika Anda perlu menerapkan migrasi sebagai bagian dari penyebaran otomatis, Anda dapat membuat skrip SQL yang dapat dipratinjau sebelum eksekusi.
Atau, Anda dapat menggunakan EnsureCreatedAsync untuk membuat database baru yang berisi data terkelola, misalnya untuk database pengujian atau saat menggunakan penyedia dalam memori atau database non-relasional apa pun. Perhatikan bahwa jika database sudah ada, EnsureCreatedAsync tidak akan memperbarui skema atau data terkelola dalam database. Untuk database relasional, Anda tidak boleh memanggil EnsureCreatedAsync jika Anda berencana menggunakan Migrasi.
Catatan
Mengisi database menggunakan metode HasData dulunya disebut sebagai "data seeding". Penamaan ini menetapkan ekspektasi yang salah, karena fitur ini memiliki sejumlah batasan dan hanya sesuai untuk jenis data tertentu. Itulah sebabnya kami memutuskan untuk mengganti namanya menjadi "data terkelola model". Metode UseSeeding dan UseAsyncSeeding harus digunakan untuk penyemaian data tujuan umum.
Batasan pengelolaan data model
Jenis data ini dikelola oleh migrasi dan skrip untuk memperbarui data yang sudah ada dalam database perlu dibuat tanpa menyambungkan ke database. Ini memberlakukan beberapa batasan:
- Nilai kunci utama perlu ditentukan meskipun biasanya dihasilkan oleh database. Ini akan digunakan untuk mendeteksi perubahan data antara migrasi.
- Data yang disisipkan sebelumnya akan dihapus jika kunci utama diubah dengan cara apa pun.
Oleh karena itu fitur ini paling berguna untuk data statis yang tidak diharapkan berubah di luar migrasi dan tidak bergantung pada hal lain dalam database, misalnya kode pos.
Jika skenario Anda menyertakan salah satu hal berikut, disarankan untuk menggunakan UseSeeding metode dan UseAsyncSeeding yang dijelaskan di bagian pertama:
- Data sementara untuk pengujian
- Data yang bergantung pada status database
- Data yang besar (data awal ditangkap dalam cuplikan migrasi, dan data besar dapat dengan cepat mengakibatkan file yang sangat besar dan kinerja yang terdegradasi).
- Data yang membutuhkan nilai kunci yang akan dihasilkan oleh database, termasuk entitas yang menggunakan kunci alternatif sebagai identitas
- Data yang memerlukan transformasi khusus (yang tidak ditangani oleh konversi nilai), seperti beberapa penghashan kata sandi
- Data yang memerlukan panggilan ke API eksternal, seperti pembuatan peran dan pengguna dalam ASP.NET Core Identity
- Data yang tidak tetap dan deterministik, seperti penyemaian ke
DateTime.Now.
Kustomisasi migrasi manual
Saat migrasi ditambahkan, perubahan pada data yang ditentukan dengan HasData diubah menjadi panggilan ke InsertData(), UpdateData(), dan DeleteData(). Salah satu cara mengatasi beberapa batasan HasData adalah dengan menambahkan panggilan atau operasi kustom ini secara manual ke migrasi sebagai gantinya.
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 }
});