Bagikan melalui


Pemetaan tabel tingkat lanjut

EF Core menawarkan banyak fleksibilitas dalam hal pemetaan jenis entitas ke tabel dalam database. Ini menjadi lebih berguna ketika Anda perlu menggunakan database yang tidak dibuat oleh EF.

Teknik di bawah ini dijelaskan dalam hal tabel, tetapi hasil yang sama juga dapat dicapai saat pemetaan ke tampilan.

Pemisahan tabel

EF Core memungkinkan untuk memetakan dua atau beberapa entitas ke satu baris. Ini disebut pemisahan tabel atau berbagi tabel.

Konfigurasi

Untuk menggunakan tabel yang memisahkan jenis entitas perlu dipetakan ke tabel yang sama, minta kunci utama dipetakan ke kolom yang sama dan setidaknya satu hubungan dikonfigurasi antara kunci utama satu jenis entitas dan yang lain dalam tabel yang sama.

Skenario umum untuk pemisahan tabel hanya menggunakan subset kolom dalam tabel untuk performa atau enkapsulasi yang lebih besar.

Dalam contoh Order ini mewakili subset dari DetailedOrder.

public class Order
{
    public int Id { get; set; }
    public OrderStatus? Status { get; set; }
    public DetailedOrder DetailedOrder { get; set; }
}
public class DetailedOrder
{
    public int Id { get; set; }
    public OrderStatus? Status { get; set; }
    public string BillingAddress { get; set; }
    public string ShippingAddress { get; set; }
    public byte[] Version { get; set; }
}

Selain konfigurasi yang diperlukan, kami memanggil Property(o => o.Status).HasColumnName("Status") untuk memetakan DetailedOrder.Status ke kolom yang sama dengan Order.Status.

modelBuilder.Entity<DetailedOrder>(
    dob =>
    {
        dob.ToTable("Orders");
        dob.Property(o => o.Status).HasColumnName("Status");
    });

modelBuilder.Entity<Order>(
    ob =>
    {
        ob.ToTable("Orders");
        ob.Property(o => o.Status).HasColumnName("Status");
        ob.HasOne(o => o.DetailedOrder).WithOne()
            .HasForeignKey<DetailedOrder>(o => o.Id);
        ob.Navigation(o => o.DetailedOrder).IsRequired();
    });

Tip

Lihat proyek sampel lengkap untuk konteks selengkapnya.

Penggunaan

Menyimpan dan mengkueri entitas menggunakan pemisahan tabel dilakukan dengan cara yang sama seperti entitas lain:

using (var context = new TableSplittingContext())
{
    context.Database.EnsureDeleted();
    context.Database.EnsureCreated();

    context.Add(
        new Order
        {
            Status = OrderStatus.Pending,
            DetailedOrder = new DetailedOrder
            {
                Status = OrderStatus.Pending,
                ShippingAddress = "221 B Baker St, London",
                BillingAddress = "11 Wall Street, New York"
            }
        });

    context.SaveChanges();
}

using (var context = new TableSplittingContext())
{
    var pendingCount = context.Orders.Count(o => o.Status == OrderStatus.Pending);
    Console.WriteLine($"Current number of pending orders: {pendingCount}");
}

using (var context = new TableSplittingContext())
{
    var order = context.DetailedOrders.First(o => o.Status == OrderStatus.Pending);
    Console.WriteLine($"First pending order will ship to: {order.ShippingAddress}");
}

Entitas dependen opsional

Jika semua kolom yang digunakan oleh entitas dependen berada NULL dalam database, maka tidak ada instans untuk kolom tersebut yang akan dibuat saat dikueri. Ini memungkinkan pemodelan entitas dependen opsional, di mana properti hubungan pada prinsipal akan null. Perhatikan bahwa ini juga akan terjadi jika semua properti dependen bersifat opsional dan diatur ke null, yang mungkin tidak diharapkan.

Namun, pemeriksaan tambahan dapat memengaruhi performa kueri. Selain itu, jika jenis entitas dependen memiliki dependen sendiri, maka menentukan apakah instans harus dibuat menjadi tidak sepele. Untuk menghindari masalah ini, jenis entitas dependen dapat ditandai sebagaimana diperlukan, lihat Dependen satu-ke-satu yang diperlukan untuk informasi selengkapnya.

Token konkurensi

Jika salah satu jenis entitas yang berbagi tabel memiliki token konkurensi, maka harus disertakan dalam semua jenis entitas lainnya juga. Ini diperlukan untuk menghindari nilai token konkurensi basi ketika hanya salah satu entitas yang dipetakan ke tabel yang sama yang diperbarui.

Untuk menghindari mengekspos token konkurensi ke kode yang mengonsumsi, ada kemungkinan membuat token sebagai properti bayangan:

modelBuilder.Entity<Order>()
    .Property<byte[]>("Version").IsRowVersion().HasColumnName("Version");

modelBuilder.Entity<DetailedOrder>()
    .Property(o => o.Version).IsRowVersion().HasColumnName("Version");

Warisan

Disarankan untuk membaca halaman khusus tentang warisan sebelum melanjutkan dengan bagian ini.

Jenis dependen yang menggunakan pemisahan tabel dapat memiliki hierarki pewarisan, tetapi ada beberapa batasan:

  • Jenis entitas dependen tidak dapat menggunakan pemetaan TPC karena jenis turunan tidak akan dapat memetakan ke tabel yang sama.
  • Jenis entitas dependen dapat menggunakan pemetaan TPT, tetapi hanya jenis entitas akar yang dapat menggunakan pemisahan tabel.
  • Jika jenis entitas utama menggunakan TPC, maka hanya jenis entitas yang tidak memiliki turunan yang dapat menggunakan pemisahan tabel. Jika tidak, kolom dependen perlu diduplikasi pada tabel yang sesuai dengan jenis turunan, mempersulit semua interaksi.

Pemisahan entitas

EF Core memungkinkan untuk memetakan entitas ke baris dalam dua tabel atau lebih. Ini disebut pemisahan entitas.

Konfigurasi

Misalnya, pertimbangkan database dengan tiga tabel yang menyimpan data pelanggan:

  • Tabel Customers untuk informasi pelanggan
  • Tabel PhoneNumbers untuk nomor telepon pelanggan
  • Tabel Addresses untuk alamat pelanggan

Berikut adalah definisi untuk tabel ini di SQL Server:

CREATE TABLE [Customers] (
    [Id] int NOT NULL IDENTITY,
    [Name] nvarchar(max) NOT NULL,
    CONSTRAINT [PK_Customers] PRIMARY KEY ([Id])
);
    
CREATE TABLE [PhoneNumbers] (
    [CustomerId] int NOT NULL,
    [PhoneNumber] nvarchar(max) NULL,
    CONSTRAINT [PK_PhoneNumbers] PRIMARY KEY ([CustomerId]),
    CONSTRAINT [FK_PhoneNumbers_Customers_CustomerId] FOREIGN KEY ([CustomerId]) REFERENCES [Customers] ([Id]) ON DELETE CASCADE
);

CREATE TABLE [Addresses] (
    [CustomerId] int NOT NULL,
    [Street] nvarchar(max) NOT NULL,
    [City] nvarchar(max) NOT NULL,
    [PostCode] nvarchar(max) NULL,
    [Country] nvarchar(max) NOT NULL,
    CONSTRAINT [PK_Addresses] PRIMARY KEY ([CustomerId]),
    CONSTRAINT [FK_Addresses_Customers_CustomerId] FOREIGN KEY ([CustomerId]) REFERENCES [Customers] ([Id]) ON DELETE CASCADE
);

Masing-masing tabel ini biasanya akan dipetakan ke jenis entitas mereka sendiri, dengan hubungan antara jenis. Namun, jika ketiga tabel selalu digunakan bersama-sama, maka akan lebih nyaman untuk memetakan semuanya ke satu jenis entitas. Contohnya:

public class Customer
{
    public Customer(string name, string street, string city, string? postCode, string country)
    {
        Name = name;
        Street = street;
        City = city;
        PostCode = postCode;
        Country = country;
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public string? PhoneNumber { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string? PostCode { get; set; }
    public string Country { get; set; }
}

Ini dicapai di EF7 dengan memanggil SplitToTable untuk setiap pemisahan dalam jenis entitas. Misalnya, kode berikut membagi Customer jenis entitas ke Customerstabel , , PhoneNumbersdan Addresses yang ditunjukkan di atas:

modelBuilder.Entity<Customer>(
    entityBuilder =>
    {
        entityBuilder
            .ToTable("Customers")
            .SplitToTable(
                "PhoneNumbers",
                tableBuilder =>
                {
                    tableBuilder.Property(customer => customer.Id).HasColumnName("CustomerId");
                    tableBuilder.Property(customer => customer.PhoneNumber);
                })
            .SplitToTable(
                "Addresses",
                tableBuilder =>
                {
                    tableBuilder.Property(customer => customer.Id).HasColumnName("CustomerId");
                    tableBuilder.Property(customer => customer.Street);
                    tableBuilder.Property(customer => customer.City);
                    tableBuilder.Property(customer => customer.PostCode);
                    tableBuilder.Property(customer => customer.Country);
                });
    });

Perhatikan juga bahwa, jika perlu, nama kolom yang berbeda dapat ditentukan untuk setiap tabel. Untuk mengonfigurasi nama kolom untuk tabel utama, lihat Konfigurasi faset khusus tabel.

Mengonfigurasi penautan kunci asing

FK yang menautkan tabel yang dipetakan menargetkan properti yang sama tempat tabel dinyatakan. Biasanya itu tidak akan dibuat dalam database, karena akan berlebihan. Tetapi ada pengecualian ketika jenis entitas dipetakan ke lebih dari satu tabel. Untuk mengubah fasetnya, Anda dapat menggunakan konfigurasi hubungan Fluent API:

modelBuilder.Entity<Customer>()
    .HasOne<Customer>()
    .WithOne()
    .HasForeignKey<Customer>(a => a.Id)
    .OnDelete(DeleteBehavior.Restrict);

Batasan

  • Pemisahan entitas tidak dapat digunakan untuk jenis entitas dalam hierarki.
  • Untuk baris apa pun dalam tabel utama harus ada baris di setiap tabel terpisah (fragmen tidak opsional).

Konfigurasi faset khusus tabel

Beberapa pola pemetaan mengakibatkan properti CLR yang sama dipetakan ke kolom di masing-masing dari beberapa tabel yang berbeda. EF7 memungkinkan kolom ini memiliki nama yang berbeda. Misalnya, pertimbangkan hierarki pewarisan sederhana:

public abstract class Animal
{
    public int Id { get; set; }
    public string Breed { get; set; } = null!;
}

public class Cat : Animal
{
    public string? EducationalLevel { get; set; }
}

public class Dog : Animal
{
    public string? FavoriteToy { get; set; }
}

Dengan strategi pemetaan warisan TPT, jenis ini akan dipetakan ke tiga tabel. Namun, kolom kunci utama di setiap tabel mungkin memiliki nama yang berbeda. Contohnya:

CREATE TABLE [Animals] (
    [Id] int NOT NULL IDENTITY,
    [Breed] nvarchar(max) NOT NULL,
    CONSTRAINT [PK_Animals] PRIMARY KEY ([Id])
);

CREATE TABLE [Cats] (
    [CatId] int NOT NULL,
    [EducationalLevel] nvarchar(max) NULL,
    CONSTRAINT [PK_Cats] PRIMARY KEY ([CatId]),
    CONSTRAINT [FK_Cats_Animals_CatId] FOREIGN KEY ([CatId]) REFERENCES [Animals] ([Id]) ON DELETE CASCADE
);

CREATE TABLE [Dogs] (
    [DogId] int NOT NULL,
    [FavoriteToy] nvarchar(max) NULL,
    CONSTRAINT [PK_Dogs] PRIMARY KEY ([DogId]),
    CONSTRAINT [FK_Dogs_Animals_DogId] FOREIGN KEY ([DogId]) REFERENCES [Animals] ([Id]) ON DELETE CASCADE
);

EF7 memungkinkan pemetaan ini dikonfigurasi menggunakan penyusun tabel berlapis:

modelBuilder.Entity<Animal>().ToTable("Animals");

modelBuilder.Entity<Cat>()
    .ToTable(
        "Cats",
        tableBuilder => tableBuilder.Property(cat => cat.Id).HasColumnName("CatId"));

modelBuilder.Entity<Dog>()
    .ToTable(
        "Dogs",
        tableBuilder => tableBuilder.Property(dog => dog.Id).HasColumnName("DogId"));

Dengan pemetaan pewarisan TPC, Breed properti juga dapat dipetakan ke nama kolom yang berbeda dalam tabel yang berbeda. Misalnya, pertimbangkan tabel TPC berikut:

CREATE TABLE [Cats] (
    [CatId] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]),
    [CatBreed] nvarchar(max) NOT NULL,
    [EducationalLevel] nvarchar(max) NULL,
    CONSTRAINT [PK_Cats] PRIMARY KEY ([CatId])
);

CREATE TABLE [Dogs] (
    [DogId] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]),
    [DogBreed] nvarchar(max) NOT NULL,
    [FavoriteToy] nvarchar(max) NULL,
    CONSTRAINT [PK_Dogs] PRIMARY KEY ([DogId])
);

EF7 mendukung pemetaan tabel ini:

modelBuilder.Entity<Animal>().UseTpcMappingStrategy();

modelBuilder.Entity<Cat>()
    .ToTable(
        "Cats",
        builder =>
        {
            builder.Property(cat => cat.Id).HasColumnName("CatId");
            builder.Property(cat => cat.Breed).HasColumnName("CatBreed");
        });

modelBuilder.Entity<Dog>()
    .ToTable(
        "Dogs",
        builder =>
        {
            builder.Property(dog => dog.Id).HasColumnName("DogId");
            builder.Property(dog => dog.Breed).HasColumnName("DogBreed");
        });