Jenis Entitas yang Dimiliki

EF Core memungkinkan Anda memodelkan jenis entitas yang hanya dapat muncul pada properti navigasi jenis entitas lain. Ini disebut jenis entitas yang dimiliki. Entitas yang berisi jenis entitas yang dimiliki adalah pemiliknya.

Entitas yang dimiliki pada dasarnya adalah bagian dari pemilik dan tidak dapat ada tanpanya, mereka secara konseptual mirip dengan agregat. Ini berarti bahwa sesuai definisi, entitas yang dimiliki berada pada pihak yang bergantung dalam hubungan dengan pemilik.

Petunjuk / Saran

Jika Anda memodelkan objek nilai - objek tanpa identitasnya sendiri, seperti Address atau Coordinate - pertimbangkan untuk menggunakan jenis kompleks alih-alih jenis entitas yang dimiliki. Tidak seperti jenis yang dimiliki, jenis kompleks memiliki semantik nilai dan tidak ada kunci tersembunyi, yang menghindari sejumlah perangkap; lihat Jenis kompleks vs. jenis entitas yang dimiliki untuk perbandingan.

Mengonfigurasi jenis sebagai milik

Di sebagian besar penyedia, jenis entitas tidak pernah dikonfigurasi sebagai milik berdasarkan konvensi - Anda harus secara eksplisit menggunakan metode OwnsOne di OnModelCreating atau menambahkan anotasi pada jenis dengan OwnedAttribute untuk mengonfigurasi jenis sebagai milik. Penyedia Azure Cosmos DB adalah pengecualian untuk ini. Karena Azure Cosmos DB adalah database dokumen, penyedia mengonfigurasi semua jenis entitas terkait sebagaimana dimiliki secara default.

Dalam contoh ini, StreetAddress adalah jenis tanpa properti identitas. Ini digunakan sebagai properti dari jenis Pesanan untuk menentukan alamat pengiriman untuk pesanan tertentu.

Kita dapat menggunakan OwnedAttribute untuk memperlakukannya sebagai entitas yang dimiliki saat direferensikan dari jenis entitas lain:

[Owned]
public class StreetAddress
{
    public string Street { get; set; }
    public string City { get; set; }
}
public class Order
{
    public int Id { get; set; }
    public StreetAddress ShippingAddress { get; set; }
}

Dimungkinkan juga untuk menggunakan metode OwnsOne di OnModelCreating untuk menentukan bahwa properti ShippingAddress adalah Entitas Milik dari jenis entitas Order serta mengonfigurasi aspek tambahan jika diperlukan.

modelBuilder.Entity<Order>().OwnsOne(p => p.ShippingAddress);

ShippingAddress Jika properti bersifat privat dalam Order jenis , Anda dapat menggunakan versi OwnsOne string metode:

modelBuilder.Entity<Order>().OwnsOne(typeof(StreetAddress), "ShippingAddress");

Model di atas dipetakan ke skema database berikut:

Cuplikan layar model database untuk entitas yang berisi referensi yang dimiliki

Lihat proyek sampel lengkap untuk konteks selengkapnya.

Petunjuk / Saran

Jenis entitas yang dimiliki dapat ditandai sebagai wajib, lihat dependen wajib satu-ke-satu untuk informasi selengkapnya.

Kunci implisit

Jenis milik yang dikonfigurasi dengan OwnsOne atau ditemukan melalui navigasi referensi selalu memiliki hubungan satu-ke-satu dengan pemiliknya, oleh karena itu mereka tidak memerlukan nilai kunci tersendiri karena nilai kunci asing bersifat unik. Dalam contoh sebelumnya, StreetAddress jenis tidak perlu menentukan properti kunci.

Untuk memahami bagaimana EF Core melacak objek ini, berguna untuk mengetahui bahwa kunci primer dibuat sebagai shadow property untuk tipe yang dimiliki. Nilai kunci dari instans tipe yang dimiliki akan sama dengan nilai kunci dari instans pemilik.

Koleksi tipe yang dimiliki

Untuk mengonfigurasi kumpulan jenis yang dimiliki, gunakan OwnsMany di OnModelCreating.

Tipe data yang dimiliki memerlukan kunci primer. Jika tidak ada properti kandidat yang baik pada jenis .NET, EF Core dapat mencoba membuatnya. Namun, ketika jenis yang dimiliki didefinisikan melalui koleksi, tidak cukup untuk hanya membuat properti bayangan untuk bertindak sebagai kunci asing ke pemilik dan kunci utama instans yang dimiliki, seperti yang kita lakukan untuk OwnsOne: mungkin ada beberapa instans jenis yang dimiliki untuk setiap pemilik, dan karenanya kunci pemilik tidak cukup untuk memberikan identitas unik untuk setiap instans yang dimiliki.

Dua solusi paling mudah untuk ini adalah:

  • Menentukan kunci primer pengganti pada properti baru yang independen dari kunci asing yang menunjuk ke pemilik. Nilai yang terkandung harus unik di semua pemilik (misalnya jika Induk {1} memiliki Anak {1}, maka Induk {2} tidak dapat memiliki Anak {1}), sehingga nilai tidak memiliki arti yang melekat. Karena kunci asing bukan bagian dari kunci utama nilainya dapat diubah, sehingga Anda dapat memindahkan anak dari satu induk ke induk lainnya, namun ini biasanya bertentangan dengan semantik agregat.
  • Menggunakan kunci asing dan atribut tambahan sebagai kunci komposit. Nilai properti tambahan sekarang hanya perlu unik untuk induk tertentu (jadi jika Induk {1} memiliki Anak {1,1} maka Induk {2} masih dapat memiliki Anak {2,1}). Dengan menjadikan bagian kunci asing dari kunci utama hubungan antara pemilik dan entitas yang dimiliki menjadi tidak dapat diubah dan mencerminkan semantik agregat dengan lebih baik. Inilah yang dilakukan EF Core secara default.

Dalam contoh ini kita akan menggunakan Distributor kelas .

public class Distributor
{
    public int Id { get; set; }
    public ICollection<StreetAddress> ShippingCenters { get; set; }
}

Secara default, kunci primer yang digunakan untuk jenis yang dimiliki dan diakses melalui properti navigasi ShippingCenters akan menjadi ("DistributorId", "Id"), di mana "DistributorId" adalah FK dan "Id" adalah int yang unik.

Untuk mengonfigurasi kunci primer berbeda, panggil HasKey.

modelBuilder.Entity<Distributor>().OwnsMany(
    p => p.ShippingCenters, a =>
    {
        a.WithOwner().HasForeignKey("OwnerId");
        a.Property<int>("Id");
        a.HasKey("Id");
    });

Model di atas dipetakan ke skema database berikut:

Cuplikan layar model database untuk entitas yang berisi koleksi yang dimiliki

Pemetaan tipe yang dimiliki dengan pemisahan tabel

Saat menggunakan database relasional, secara default jenis referensi yang dimiliki dipetakan ke tabel yang sama dengan pemilik. Ini mengharuskan pemisahan tabel menjadi dua: beberapa kolom akan digunakan untuk menyimpan data pemilik, dan beberapa kolom akan digunakan untuk menyimpan data entitas yang dimiliki. Ini adalah fitur umum yang dikenal sebagai pemisahan tabel.

Secara default, EF Core akan memberi nama kolom database untuk properti jenis entitas yang dimiliki mengikuti pola Navigation_OwnedEntityProperty. StreetAddress Oleh karena itu properti akan muncul dalam tabel 'Pesanan' dengan nama 'ShippingAddress_Street' dan 'ShippingAddress_City'.

Anda dapat menggunakan metode untuk mengganti nama kolom tersebut HasColumnName .

modelBuilder.Entity<Order>().OwnsOne(
    o => o.ShippingAddress,
    sa =>
    {
        sa.Property(p => p.Street).HasColumnName("ShipsToStreet");
        sa.Property(p => p.City).HasColumnName("ShipsToCity");
    });

Catatan

Sebagian besar metode konfigurasi jenis entitas normal seperti Ignore dapat dipanggil dengan cara yang sama.

Berbagi jenis .NET yang sama di antara beberapa jenis yang dimiliki

Jenis entitas yang dimiliki dapat memiliki jenis .NET yang sama dengan jenis entitas milik lain, oleh karena itu jenis .NET mungkin tidak cukup untuk mengidentifikasi jenis yang dimiliki.

Dalam kasus tersebut, properti yang menghubungkan dari pemilik ke entitas yang dimiliki menjadi navigasi penentu untuk jenis entitas yang dimiliki. Dari perspektif EF Core, navigasi penentu adalah bagian dari identitas tipe bersamaan dengan tipe .NET.

Misalnya, di kelas ShippingAddress berikut dan BillingAddress keduanya adalah jenis .NET yang sama, StreetAddress.

public class OrderDetails
{
    public DetailedOrder Order { get; set; }
    public StreetAddress BillingAddress { get; set; }
    public StreetAddress ShippingAddress { get; set; }
}

Untuk memahami bagaimana EF Core akan membedakan instans yang dilacak dari objek ini, mungkin berguna untuk berpikir bahwa navigasi yang mendefinisikan telah menjadi bagian dari kunci instans tersebut bersama dengan nilai kunci pemilik dan jenis .NET dari jenis yang dimiliki.

Jenis milik berlapis

Dalam contoh ini, OrderDetails memiliki BillingAddress dan ShippingAddress, yang keduanya adalah jenis StreetAddress. Kemudian OrderDetails dimiliki oleh tipe DetailedOrder.

public class DetailedOrder
{
    public int Id { get; set; }
    public OrderDetails OrderDetails { get; set; }
    public OrderStatus Status { get; set; }
}
public enum OrderStatus
{
    Pending,
    Shipped
}

Setiap navigasi ke tipe yang dimiliki menentukan jenis entitas terpisah dengan konfigurasi yang sepenuhnya terpisah.

Selain jenis yang dimiliki berlapis, jenis yang dimiliki dapat mereferensikan entitas reguler yang dapat berupa pemilik atau entitas yang berbeda selama entitas yang dimiliki berada di sisi dependen. Kemampuan ini membedakan jenis entitas milik dari jenis kompleks di EF6.

public class OrderDetails
{
    public DetailedOrder Order { get; set; }
    public StreetAddress BillingAddress { get; set; }
    public StreetAddress ShippingAddress { get; set; }
}

Mengonfigurasi tipe yang dimiliki

Dimungkinkan untuk menautkan metode OwnsOne secara berantai untuk mengonfigurasi model ini.

modelBuilder.Entity<DetailedOrder>().OwnsOne(
    p => p.OrderDetails, od =>
    {
        od.WithOwner(d => d.Order);
        od.Navigation(d => d.Order).UsePropertyAccessMode(PropertyAccessMode.Property);
        od.OwnsOne(c => c.BillingAddress);
        od.OwnsOne(c => c.ShippingAddress);
    });

Perhatikan penggunaan panggilan WithOwner untuk menentukan properti navigasi yang mengarah kembali ke pemilik. Untuk mendefinisikan navigasi ke jenis entitas pemilik yang bukan bagian dari hubungan kepemilikan, WithOwner() harus dipanggil tanpa argumen apa pun.

Dimungkinkan juga untuk mencapai hasil ini menggunakan OwnedAttribute pada OrderDetails dan StreetAddress.

Selain itu, perhatikan panggilan Navigation. Properti navigasi ke jenis yang dimiliki dapat dikonfigurasi lebih lanjut dengan cara yang sama seperti properti navigasi yang tidak dimiliki.

Model di atas dipetakan ke skema database berikut:

Cuplikan layar model database untuk entitas yang berisi referensi milik berlapis

Menyimpan jenis yang dimiliki dalam tabel terpisah

Juga tidak seperti jenis kompleks EF6, jenis yang dimiliki dapat disimpan dalam tabel terpisah dari pemilik. Untuk menggantikan konvensi yang memetakan tipe yang dimiliki ke tabel yang sama dengan pemilik, Anda cukup memanggil ToTable dan memberikan nama tabel yang berbeda. Contoh berikut akan memetakan OrderDetails dan dua alamatnya ke tabel terpisah dari DetailedOrder:

modelBuilder.Entity<DetailedOrder>().OwnsOne(p => p.OrderDetails, od => { od.ToTable("OrderDetails"); });

Dimungkinkan juga untuk menggunakan TableAttribute untuk mencapai ini, tetapi perhatikan bahwa ini akan gagal jika ada beberapa navigasi ke jenis yang dimiliki karena dalam hal ini beberapa jenis entitas akan dipetakan ke tabel yang sama.

Mengkueri tipe yang dimiliki

Saat melakukan kueri pada pemilik, jenis yang dimiliki akan dimasukkan secara default. Tidak perlu menggunakan metode Include, bahkan jika jenis milik disimpan dalam tabel terpisah. Berdasarkan model yang dijelaskan sebelumnya, kueri berikut akan mendapatkan Order, OrderDetails dan dua StreetAddresses yang dimiliki dari database.

var order = await context.DetailedOrders.FirstAsync(o => o.Status == OrderStatus.Pending);
Console.WriteLine($"First pending order will ship to: {order.OrderDetails.ShippingAddress.City}");

Batasan

Beberapa batasan ini sangat mendasar tentang cara kerja jenis entitas yang dimiliki, tetapi beberapa lainnya adalah batasan yang mungkin dapat kami hapus dalam rilis mendatang:

Pembatasan berdasarkan desain

  • Anda tidak dapat membuat DbSet<T> untuk jenis yang dimiliki.
  • Anda tidak dapat memanggil Entity<T>() dengan tipe milik pada ModelBuilder.
  • Instans jenis entitas yang dimiliki tidak dapat dibagikan oleh beberapa pemilik (ini adalah skenario terkenal untuk objek nilai yang tidak dapat diimplementasikan menggunakan jenis entitas yang dimiliki).

Kekurangan saat ini

  • Jenis entitas yang dimiliki tidak dapat memiliki hierarki pewarisan

Kekurangan dalam versi sebelumnya

  • Dalam EF Core 2.x, navigasi referensi ke tipe entitas yang dimiliki tidak boleh null kecuali secara eksplisit dipetakan ke tabel terpisah dari pemiliknya.
  • Pada EF Core 3.x, kolom untuk tipe entitas yang dimiliki yang dipetakan ke tabel yang sama dengan pemilik selalu ditandai sebagai nullable.