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 entitas yang dimiliki berdasarkan definisi pada sisi dependen hubungan dengan pemilik.
Mengonfigurasi jenis sebagai milik
Di sebagian besar penyedia, jenis entitas tidak pernah dikonfigurasi seperti yang dimiliki oleh konvensi - Anda harus secara eksplisit menggunakan OwnsOne
metode di OnModelCreating
atau membuat anotasi jenis dengan OwnedAttribute
untuk mengonfigurasi jenis seperti yang dimiliki. 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 OwnsOne
metode di OnModelCreating
untuk menentukan bahwa ShippingAddress
properti adalah Entitas Milik dari Order
jenis entitas dan untuk mengonfigurasi faset 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:
Lihat proyek sampel lengkap untuk konteks selengkapnya.
Tip
Jenis entitas yang dimiliki dapat ditandai sebagaimana diperlukan, lihat Dependen satu-ke-satu yang diperlukan untuk informasi selengkapnya.
Kunci implisit
Jenis yang dimiliki dikonfigurasi dengan OwnsOne
atau ditemukan melalui navigasi referensi selalu memiliki hubungan satu-ke-satu dengan pemilik, oleh karena itu mereka tidak memerlukan nilai kunci mereka sendiri karena nilai kunci asing 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 properti bayangan untuk jenis yang dimiliki. Nilai kunci instans jenis yang dimiliki akan sama dengan nilai kunci instans pemilik.
Kumpulan jenis yang dimiliki
Untuk mengonfigurasi kumpulan jenis yang dimiliki, gunakan OwnsMany
di OnModelCreating
.
Jenis 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 properti tambahan sebagai kunci komposit. Nilai properti tambahan sekarang hanya perlu unik untuk induk tertentu (jadi jika Induk memiliki Anak {1,1} maka Induk {2} masih dapat memiliki Turunan {2,1}).{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 yang direferensikan melalui ShippingCenters
properti navigasi akan menjadi ("DistributorId", "Id")
tempat "DistributorId"
FK dan "Id"
merupakan nilai unik int
.
Untuk mengonfigurasi panggilan HasKey
kunci primer yang berbeda.
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:
Pemetaan jenis 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 menunjuk dari pemilik ke entitas yang dimiliki menjadi navigasi yang menentukan jenis entitas yang dimiliki. Dari perspektif EF Core, navigasi yang menentukan adalah bagian dari identitas jenis bersama jenis .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 terlacak dari objek ini, mungkin berguna untuk berpikir bahwa navigasi yang menentukan telah menjadi bagian dari kunci instans bersama nilai kunci pemilik dan jenis .NET dari jenis yang dimiliki.
Jenis milik berlapis
Dalam contoh OrderDetails
ini memiliki BillingAddress
dan ShippingAddress
, yang keduanya StreetAddress
adalah jenis. Kemudian OrderDetails
dimiliki oleh jenis 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 jenis yang dimiliki menentukan jenis entitas terpisah dengan konfigurasi yang sepenuhnya independen.
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 menetapkan jenis entitas yang dimiliki selain dari jenis kompleks di EF6.
public class OrderDetails
{
public DetailedOrder Order { get; set; }
public StreetAddress BillingAddress { get; set; }
public StreetAddress ShippingAddress { get; set; }
}
Mengonfigurasi jenis yang dimiliki
Dimungkinkan untuk menautkan OwnsOne
metode dalam panggilan fasih 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 panggilan yang WithOwner
digunakan untuk menentukan properti navigasi yang menunjuk kembali ke pemilik. Untuk menentukan navigasi ke jenis entitas pemilik yang bukan bagian dari hubungan WithOwner()
kepemilikan harus dipanggil tanpa argumen apa pun.
Dimungkinkan juga untuk mencapai hasil ini menggunakan OwnedAttribute
pada dan OrderDetails
StreetAddress
.
Selain itu, perhatikan Navigation
panggilan. Properti navigasi ke jenis yang dimiliki dapat dikonfigurasi lebih lanjut sebagai untuk properti navigasi yang tidak dimiliki.
Model di atas dipetakan ke skema database berikut:
Menyimpan jenis yang dimiliki dalam tabel terpisah
Juga tidak seperti jenis kompleks EF6, jenis yang dimiliki dapat disimpan dalam tabel terpisah dari pemilik. Untuk mengambil alih konvensi yang memetakan jenis 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 mengkueri pemilik, jenis yang dimiliki akan disertakan secara default. Tidak perlu menggunakan metode , Include
bahkan jika jenis yang dimiliki disimpan dalam tabel terpisah. Berdasarkan model yang dijelaskan sebelumnya, kueri berikut akan mendapatkan Order
, OrderDetails
dan keduanya dimiliki StreetAddresses
dari database:
var order = context.DetailedOrders.First(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 jenis yang dimiliki padaModelBuilder
. - 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 navigasi referensi EF Core 2.x ke jenis entitas yang dimiliki tidak boleh null kecuali secara eksplisit dipetakan ke tabel terpisah dari pemilik.
- Di EF Core 3.x kolom untuk jenis entitas yang dimiliki dipetakan ke tabel yang sama dengan pemilik selalu ditandai sebagai nullable.