Bagikan melalui


Data Seeding

Penyemaian data adalah proses mengisi database dengan sekumpulan data awal.

Ada beberapa cara yang dapat dicapai dalam EF Core:

Opsi UseSeeding dan UseAsyncSeeding metode konfigurasi

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, kode di dalam UseSeeding dan UseAsyncSeeding metode dilindungi oleh mekanisme penguncian migrasi untuk mencegah masalah konkurensi.

Metode penyemaian baru disebut sebagai bagian EnsureCreated dari operasi, Migrate dan dotnet ef database update perintah, bahkan jika tidak ada perubahan model dan tidak ada migrasi yang diterapkan.

Tip

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. Alat EF Core saat ini bergantung pada versi sinkron metode dan tidak akan menyemai database dengan benar jika UseSeeding metode tidak diimplementasikan.

Logika inisialisasi kustom

Cara mudah dan kuat untuk melakukan penyemaian data adalah dengan menggunakan DbContext.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 publikasi.

Data terkelola 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 Country untuk 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.

Tip

Jika Anda perlu menerapkan migrasi sebagai bagian dari penyebaran otomatis, Anda dapat membuat skrip SQL yang dapat dipratinjau sebelum eksekusi.

Atau, Anda dapat menggunakan context.Database.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 yang HasData digunakan untuk disebut sebagai "seeding data". 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". UseSeeding metode dan UseAsyncSeeding harus digunakan untuk penyemaian data tujuan umum.

Batasan data terkelola 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 penyemaian diambil dalam rekam jepret migrasi, dan data besar dapat dengan cepat menyebabkan file besar dan performa yang terdegradasi).
  • Data yang membutuhkan nilai kunci yang akan dihasilkan oleh database, termasuk entitas yang menggunakan kunci alternatif sebagai identitas
  • Data yang memerlukan transformasi kustom (yang tidak ditangani oleh konversi nilai), seperti beberapa hash kata sandi
  • Data yang memerlukan panggilan ke API eksternal, seperti peran identitas inti ASP.NET dan pembuatan pengguna
  • 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 }
    });