Bagikan melalui


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:

Screenshot of the database model for entity containing owned reference

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 HasKeykunci 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:

Sceenshot of the database model for entity containing owned collection

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 OrderDetailsStreetAddress.

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:

Screenshot of the database model for entity containing nested owned references

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 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 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.