Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
EF dapat memetakan hierarki jenis .NET ke database. Ini memungkinkan Anda untuk menulis entitas .NET Anda dalam kode seperti biasa, menggunakan jenis dasar dan turunan, dan EF secara mulus membuat skema database yang sesuai, memproses kueri, dll. Detail aktual tentang bagaimana hierarki jenis dipetakan bergantung pada penyedia; halaman ini menjelaskan dukungan pewarisan dalam konteks database relasional.
Pemetaan hierarki jenis entitas
Berdasarkan konvensi, EF tidak akan secara otomatis memindai jenis dasar atau turunan; ini berarti bahwa jika Anda ingin jenis CLR dalam hierarki Anda dipetakan, Anda harus secara eksplisit menentukan jenis tersebut pada model Anda. Misalnya, menentukan hanya jenis dasar hierarki tidak akan menyebabkan EF Core secara implisit menyertakan semua subtipenya.
Sampel berikut menyediakan DbSet untuk Blog
dan subkelas RssBlog
. Jika Blog
memiliki subkelas lain, subkelas tersebut tidak akan disertakan dalam model.
internal class MyContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<RssBlog> RssBlogs { get; set; }
}
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
}
public class RssBlog : Blog
{
public string RssUrl { get; set; }
}
Nota
Kolom database secara otomatis dibuat bisa null seperlunya saat menggunakan pemetaan TPH. Misalnya, RssUrl
kolom dapat diubah ke null karena instans reguler Blog
tidak memiliki properti tersebut.
Jika Anda tidak ingin mengekspos DbSet
untuk satu atau beberapa entitas dalam hierarki, Anda juga dapat menggunakan API Fasih untuk memastikan entitas tersebut disertakan dalam model.
Nasihat
Jika Anda tidak mengandalkan konvensi, Anda dapat menentukan jenis dasar secara eksplisit menggunakan HasBaseType
. Anda juga dapat menggunakan .HasBaseType((Type)null)
untuk menghapus jenis entitas dari hierarki.
Konfigurasi tabel sesuai hierarki dan diskriminator
Secara default, EF memetakan pewarisan menggunakan pola table-per-hierarchy (TPH). TPH menggunakan satu tabel untuk menyimpan data untuk semua jenis dalam hierarki, dan kolom diskriminator digunakan untuk mengidentifikasi jenis mana yang diwakili setiap baris.
Model di atas dipetakan ke skema database berikut (perhatikan kolom yang dibuat Discriminator
secara implisit, yang mengidentifikasi jenis mana Blog
yang disimpan di setiap baris).
Anda dapat mengonfigurasi nama dan jenis kolom diskriminator dan nilai yang digunakan untuk mengidentifikasi setiap jenis dalam hierarki:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasDiscriminator<string>("blog_type")
.HasValue<Blog>("blog_base")
.HasValue<RssBlog>("blog_rss");
}
Dalam contoh di atas, EF menambahkan diskriminator secara implisit sebagai properti bayangan pada entitas dasar hierarki. Properti ini dapat dikonfigurasi seperti yang lain:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property("blog_type")
.HasMaxLength(200);
}
Terakhir, diskriminator juga dapat dipetakan ke properti .NET reguler di entitas Anda:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasDiscriminator(b => b.BlogType);
modelBuilder.Entity<Blog>()
.Property(e => e.BlogType)
.HasMaxLength(200)
.HasColumnName("blog_type");
modelBuilder.Entity<RssBlog>();
}
Saat melakukan kueri untuk entitas turunan yang menggunakan pola TPH, EF Core menambahkan predikat pada kolom diskriminator sebagai bagian dari kueri. Filter ini memastikan bahwa kami tidak mendapatkan baris tambahan untuk jenis dasar atau jenis saudara yang tidak ada dalam hasil. Predikat filter ini dilewati untuk jenis entitas dasar karena kueri untuk entitas dasar akan mendapatkan hasil untuk semua entitas dalam hierarki. Saat mewujudkan hasil dari kueri, jika kita menemukan nilai diskriminator, yang tidak dipetakan ke jenis entitas apa pun dalam model, kita melemparkan pengecualian karena kita tidak tahu cara mewujudkan hasilnya. Kesalahan ini hanya terjadi jika database Anda berisi baris dengan nilai diskriminator, yang tidak dipetakan dalam model EF. Jika Anda memiliki data tersebut, maka Anda dapat menandai pemetaan diskriminator dalam model EF Core sebagai tidak lengkap untuk menunjukkan bahwa kita harus selalu menambahkan predikat filter untuk mengkueri jenis apa pun dalam hierarki.
IsComplete(false)
panggilan terhadap konfigurasi diskriminator menandai pemetaan belum lengkap.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasDiscriminator()
.IsComplete(false);
}
Kolom yang Dibagikan
Secara default, ketika dua jenis entitas saudara dalam hierarki memiliki properti dengan nama yang sama, mereka akan dipetakan ke dua kolom terpisah. Namun, jika jenisnya identik, mereka dapat dipetakan ke kolom database yang sama:
public class MyContext : DbContext
{
public DbSet<BlogBase> Blogs { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.HasColumnName("Url");
modelBuilder.Entity<RssBlog>()
.Property(b => b.Url)
.HasColumnName("Url");
}
}
public abstract class BlogBase
{
public int BlogId { get; set; }
}
public class Blog : BlogBase
{
public string Url { get; set; }
}
public class RssBlog : BlogBase
{
public string Url { get; set; }
}
Nota
Penyedia database relasional, seperti SQL Server, tidak akan secara otomatis menggunakan predikat diskriminator saat mengkueri kolom bersama saat menggunakan cast. Kueri Url = (blog as RssBlog).Url
juga akan mengembalikan Url
nilai untuk baris sejenis Blog
. Untuk membatasi kueri ke RssBlog
entitas, Anda perlu menambahkan filter secara manual pada diskriminator, seperti Url = blog is RssBlog ? (blog as RssBlog).Url : null
.
Konfigurasi tabel per jenis
Dalam pola pemetaan TPT, semua jenis dipetakan ke tabel individual. Properti yang hanya termasuk dalam jenis dasar atau jenis turunan disimpan dalam tabel yang memetakan ke jenis tersebut. Tabel yang memetakan ke jenis turunan juga menyimpan kunci asing yang menggabungkan tabel turunan dengan tabel dasar.
modelBuilder.Entity<Blog>().ToTable("Blogs");
modelBuilder.Entity<RssBlog>().ToTable("RssBlogs");
Nasihat
Alih-alih memanggil ToTable
pada setiap jenis entitas, Anda dapat memanggil modelBuilder.Entity<Blog>().UseTptMappingStrategy()
setiap jenis entitas akar dan nama tabel akan dihasilkan oleh EF.
Nasihat
Untuk mengonfigurasi nama kolom yang berbeda untuk kolom kunci utama di setiap tabel, lihat Konfigurasi faset khusus tabel.
EF akan membuat skema database berikut untuk model di atas.
CREATE TABLE [Blogs] (
[BlogId] int NOT NULL IDENTITY,
[Url] nvarchar(max) NULL,
CONSTRAINT [PK_Blogs] PRIMARY KEY ([BlogId])
);
CREATE TABLE [RssBlogs] (
[BlogId] int NOT NULL,
[RssUrl] nvarchar(max) NULL,
CONSTRAINT [PK_RssBlogs] PRIMARY KEY ([BlogId]),
CONSTRAINT [FK_RssBlogs_Blogs_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blogs] ([BlogId]) ON DELETE NO ACTION
);
Nota
Jika batasan kunci utama diganti namanya, nama baru akan diterapkan ke semua tabel yang dipetakan ke hierarki, versi EF di masa mendatang akan memungkinkan penggantian nama batasan hanya untuk tabel tertentu ketika masalah 19970 diperbaiki.
Jika Anda menggunakan konfigurasi massal, Anda dapat mengambil nama kolom untuk tabel tertentu dengan memanggil GetColumnName(IProperty, StoreObjectIdentifier).
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
var tableIdentifier = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table);
Console.WriteLine($"{entityType.DisplayName()}\t\t{tableIdentifier}");
Console.WriteLine(" Property\tColumn");
foreach (var property in entityType.GetProperties())
{
var columnName = property.GetColumnName(tableIdentifier.Value);
Console.WriteLine($" {property.Name,-10}\t{columnName}");
}
Console.WriteLine();
}
Peringatan
Dalam banyak kasus, TPT menunjukkan performa yang lebih rendah jika dibandingkan dengan TPH. Lihat dokumen performa untuk informasi selengkapnya.
Perhatian
Kolom untuk jenis turunan dipetakan ke tabel yang berbeda, oleh karena itu batasan FK komposit dan indeks yang menggunakan properti yang diwariskan dan dideklarasikan tidak dapat dibuat dalam database.
Konfigurasi tabel per jenis beton
Dalam pola pemetaan TPC, semua jenis dipetakan ke tabel individual. Setiap tabel berisi kolom untuk semua properti pada jenis entitas terkait. Ini mengatasi beberapa masalah performa umum dengan strategi TPT.
Nasihat
Tim EF mendemonstrasikan dan mendalami pemetaan TPC dalam episode Standup Komunitas Data .NET. Seperti semua episode Community Standup, Anda dapat menonton episode TPC sekarang di YouTube.
modelBuilder.Entity<Blog>().UseTpcMappingStrategy()
.ToTable("Blogs");
modelBuilder.Entity<RssBlog>()
.ToTable("RssBlogs");
Nasihat
Alih-alih memanggil ToTable
pada setiap jenis entitas yang hanya memanggil modelBuilder.Entity<Blog>().UseTpcMappingStrategy()
pada setiap jenis entitas akar akan menghasilkan nama tabel menurut konvensi.
Nasihat
Untuk mengonfigurasi nama kolom yang berbeda untuk kolom kunci utama di setiap tabel, lihat Konfigurasi faset khusus tabel.
EF akan membuat skema database berikut untuk model di atas.
CREATE TABLE [Blogs] (
[BlogId] int NOT NULL DEFAULT (NEXT VALUE FOR [BlogSequence]),
[Url] nvarchar(max) NULL,
CONSTRAINT [PK_Blogs] PRIMARY KEY ([BlogId])
);
CREATE TABLE [RssBlogs] (
[BlogId] int NOT NULL DEFAULT (NEXT VALUE FOR [BlogSequence]),
[Url] nvarchar(max) NULL,
[RssUrl] nvarchar(max) NULL,
CONSTRAINT [PK_RssBlogs] PRIMARY KEY ([BlogId])
);
Skema TPC database
Strategi TPC mirip dengan strategi TPT kecuali bahwa tabel yang berbeda dibuat untuk setiap jenis concrete dalam hierarki, tetapi tabel tidak dibuat untuk jenis abstract - itulah sebabnya disebut "table-per-concrete-type". Seperti halnya TPT, tabel itu sendiri menunjukkan jenis objek yang disimpan. Namun, tidak seperti pemetaan TPT, setiap tabel berisi kolom untuk setiap properti dalam jenis beton dan jenis dasarnya. Skema database TPC tidak dinormalisasi.
Misalnya, pertimbangkan untuk memetakan hierarki ini:
public abstract class Animal
{
protected Animal(string name)
{
Name = name;
}
public int Id { get; set; }
public string Name { get; set; }
public abstract string Species { get; }
public Food? Food { get; set; }
}
public abstract class Pet : Animal
{
protected Pet(string name)
: base(name)
{
}
public string? Vet { get; set; }
public ICollection<Human> Humans { get; } = new List<Human>();
}
public class FarmAnimal : Animal
{
public FarmAnimal(string name, string species)
: base(name)
{
Species = species;
}
public override string Species { get; }
[Precision(18, 2)]
public decimal Value { get; set; }
public override string ToString()
=> $"Farm animal '{Name}' ({Species}/{Id}) worth {Value:C} eats {Food?.ToString() ?? "<Unknown>"}";
}
public class Cat : Pet
{
public Cat(string name, string educationLevel)
: base(name)
{
EducationLevel = educationLevel;
}
public string EducationLevel { get; set; }
public override string Species => "Felis catus";
public override string ToString()
=> $"Cat '{Name}' ({Species}/{Id}) with education '{EducationLevel}' eats {Food?.ToString() ?? "<Unknown>"}";
}
public class Dog : Pet
{
public Dog(string name, string favoriteToy)
: base(name)
{
FavoriteToy = favoriteToy;
}
public string FavoriteToy { get; set; }
public override string Species => "Canis familiaris";
public override string ToString()
=> $"Dog '{Name}' ({Species}/{Id}) with favorite toy '{FavoriteToy}' eats {Food?.ToString() ?? "<Unknown>"}";
}
public class Human : Animal
{
public Human(string name)
: base(name)
{
}
public override string Species => "Homo sapiens";
public Animal? FavoriteAnimal { get; set; }
public ICollection<Pet> Pets { get; } = new List<Pet>();
public override string ToString()
=> $"Human '{Name}' ({Species}/{Id}) with favorite animal '{FavoriteAnimal?.Name ?? "<Unknown>"}'" +
$" eats {Food?.ToString() ?? "<Unknown>"}";
}
Saat menggunakan SQL Server, tabel yang dibuat untuk hierarki ini adalah:
CREATE TABLE [Cats] (
[Id] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]),
[Name] nvarchar(max) NOT NULL,
[FoodId] uniqueidentifier NULL,
[Vet] nvarchar(max) NULL,
[EducationLevel] nvarchar(max) NOT NULL,
CONSTRAINT [PK_Cats] PRIMARY KEY ([Id]));
CREATE TABLE [Dogs] (
[Id] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]),
[Name] nvarchar(max) NOT NULL,
[FoodId] uniqueidentifier NULL,
[Vet] nvarchar(max) NULL,
[FavoriteToy] nvarchar(max) NOT NULL,
CONSTRAINT [PK_Dogs] PRIMARY KEY ([Id]));
CREATE TABLE [FarmAnimals] (
[Id] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]),
[Name] nvarchar(max) NOT NULL,
[FoodId] uniqueidentifier NULL,
[Value] decimal(18,2) NOT NULL,
[Species] nvarchar(max) NOT NULL,
CONSTRAINT [PK_FarmAnimals] PRIMARY KEY ([Id]));
CREATE TABLE [Humans] (
[Id] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence]),
[Name] nvarchar(max) NOT NULL,
[FoodId] uniqueidentifier NULL,
[FavoriteAnimalId] int NULL,
CONSTRAINT [PK_Humans] PRIMARY KEY ([Id]));
Perhatikan bahwa:
Tidak ada tabel untuk
Animal
jenis atauPet
, karena ini adaabstract
dalam model objek. Ingatlah bahwa C# tidak mengizinkan instans jenis abstrak, dan oleh karena itu tidak ada situasi di mana instans jenis abstrak akan disimpan ke database.Pemetaan properti dalam jenis dasar diulang untuk setiap jenis beton. Misalnya, setiap tabel memiliki
Name
kolom, dan Kucing dan Anjing memilikiVet
kolom.Menyimpan beberapa data ke dalam database ini menghasilkan hal berikut:
Tabel Kucing
Nomor Identitas | Nama | FoodId | Dokter hewan | Tingkat Pendidikan |
---|---|---|---|---|
1 | Alice | 99ca3e98-b26d-4a0c-d4ae-08da7aca624f | Pengelly | Magister Administrasi Bisnis (MBA) |
2 | Mac | 99ca3e98-b26d-4a0c-d4ae-08da7aca624f | Pengelly | Prasekolah |
8 | Baxter | 5dc5019e-6f72-454b-d4b0-08da7aca624f | Rumah Sakit Hewan Peliharaan Bothell | Bsc |
Tabel anjing
Nomor Identitas | Nama | FoodId | Dokter hewan | FavoritToy |
---|---|---|---|---|
3 | Roti panggang | 011aaf6f-d588-4fad-d4ac-08da7aca624f | Pengelly | Pak Tupai |
Tabel FarmAnimals
Nomor Identitas | Nama | FoodId | Nilai | Jenis |
---|---|---|---|---|
4 | Clyde | 1d495075-f527-4498-d4af-08da7aca624f | 100.00 | Equus africanus asinus (keledai) |
Tabel manusia
Nomor Identitas | Nama | FoodId | FavoriteAnimalId |
---|---|---|---|
5 | Wendy | 5418fd81-7660-432f-d4b1-08da7aca624f | 2 |
6 | Arthur | 59b495d4-0414-46bf-d4ad-08da7aca624f | 1 |
9 | Katie | nul | 8 |
Perhatikan bahwa tidak seperti pemetaan TPT, semua informasi untuk satu objek terkandung dalam satu tabel. Dan, tidak seperti pemetaan TPH, tidak ada kombinasi kolom dan baris dalam tabel apa pun di mana itu tidak pernah digunakan oleh model. Kita akan melihat berikut bagaimana karakteristik ini bisa penting untuk permintaan dan penyimpanan.
Pembuatan kunci
Strategi pemetaan warisan yang dipilih memiliki konsekuensi tentang bagaimana nilai kunci primer dihasilkan dan dikelola. Kunci dalam TPH mudah, karena setiap instans entitas diwakili oleh satu baris dalam satu tabel. Segala jenis pembuatan nilai kunci dapat digunakan, dan tidak ada batasan tambahan yang diperlukan.
Untuk strategi TPT, selalu ada baris dalam tabel yang dipetakan ke tipe dasar hierarki. Segala jenis pembuatan kunci dapat digunakan pada baris ini, dan kunci untuk tabel lain ditautkan ke tabel ini menggunakan batasan kunci asing.
Hal-hal menjadi sedikit lebih rumit untuk TPC. Pertama, penting untuk dipahami bahwa EF Core mengharuskan semua entitas dalam hierarki memiliki nilai kunci yang unik, bahkan jika entitas memiliki jenis yang berbeda. Misalnya, menggunakan model contoh kami, Anjing tidak dapat memiliki nilai kunci Id yang sama dengan Kucing. Kedua, tidak seperti TPT, tidak ada tabel umum yang dapat bertindak sebagai satu tempat di mana nilai kunci hidup dan dapat dihasilkan. Ini berarti kolom sederhana Identity
tidak dapat digunakan.
Untuk database yang mendukung urutan, nilai kunci dapat dihasilkan dengan menggunakan urutan tunggal yang direferensikan dalam batasan default untuk setiap tabel. Ini adalah strategi yang digunakan dalam tabel TPC yang ditunjukkan di atas, di mana setiap tabel memiliki hal berikut:
[Id] int NOT NULL DEFAULT (NEXT VALUE FOR [AnimalSequence])
AnimalSequence
adalah urutan database yang dibuat oleh EF Core. Strategi ini digunakan secara default untuk hierarki TPC saat menggunakan penyedia database EF Core untuk SQL Server. Penyedia database untuk database lain yang mendukung urutan harus memiliki default yang sama. Strategi pembuatan kunci lainnya yang menggunakan urutan, seperti pola Hi-Lo, juga dapat digunakan dengan TPC.
Meskipun kolom Identitas standar tidak berfungsi dengan TPC, dimungkinkan untuk menggunakan kolom Identitas jika setiap tabel dikonfigurasi dengan benih dan kenaikan yang sesuai sehingga nilai yang dihasilkan untuk setiap tabel tidak akan pernah bertentangan. Contohnya:
modelBuilder.Entity<Cat>().ToTable("Cats", tb => tb.Property(e => e.Id).UseIdentityColumn(1, 4));
modelBuilder.Entity<Dog>().ToTable("Dogs", tb => tb.Property(e => e.Id).UseIdentityColumn(2, 4));
modelBuilder.Entity<FarmAnimal>().ToTable("FarmAnimals", tb => tb.Property(e => e.Id).UseIdentityColumn(3, 4));
modelBuilder.Entity<Human>().ToTable("Humans", tb => tb.Property(e => e.Id).UseIdentityColumn(4, 4));
Penting
Menggunakan strategi ini membuatnya lebih sulit untuk menambahkan jenis turunan nanti karena membutuhkan jumlah total jenis dalam hierarki untuk diketahui sebelumnya.
SQLite tidak mendukung urutan atau benih/inkremen identitas, dan oleh karena itu, pembuatan nilai kunci bilangan bulat tidak didukung saat menggunakan SQLite dengan strategi TPC. Namun, pembuatan sisi klien atau kunci unik global - seperti GUID - didukung pada database apa pun, termasuk SQLite.
Batasan kunci asing
Strategi pemetaan TPC menciptakan skema SQL yang telah dinormalisasi secara berlebihan - ini adalah salah satu alasan mengapa beberapa pendukung murni database menentang strategi ini. Misalnya, pertimbangkan kolom kunci asing FavoriteAnimalId
. Nilai dalam kolom ini harus cocok dengan nilai kunci utama beberapa hewan. Ini dapat diberlakukan dalam database dengan batasan FK sederhana saat menggunakan TPH atau TPT. Contohnya:
CONSTRAINT [FK_Animals_Animals_FavoriteAnimalId] FOREIGN KEY ([FavoriteAnimalId]) REFERENCES [Animals] ([Id])
Tetapi ketika menggunakan TPC, kunci utama untuk setiap hewan disimpan dalam tabel yang sesuai dengan jenis konkret hewan tersebut. Misalnya, kunci utama kucing disimpan di Cats.Id
kolom, sementara kunci utama anjing disimpan di Dogs.Id
kolom, dan sebagainya. Ini berarti batasan FK tidak dapat dibuat untuk hubungan ini.
Dalam praktiknya, ini bukan masalah selama aplikasi tidak mencoba menyisipkan data yang tidak valid. Misalnya, jika semua data dimasukkan oleh EF Core dan menggunakan navigasi untuk menghubungkan entitas, maka dijamin bahwa kolom FK akan berisi nilai PK yang valid setiap saat.
Ringkasan dan panduan
Singkatnya, TPH biasanya baik-baik saja untuk sebagian besar aplikasi, dan merupakan default yang baik untuk berbagai skenario, jadi jangan tambahkan kompleksitas TPC jika Anda tidak membutuhkannya. Secara khusus, jika kode Anda sebagian besar akan mengkueri entitas dari banyak jenis, seperti menulis kueri pada jenis dasar, maka lebih memilih TPH daripada TPC.
Meskipun demikian, TPC juga merupakan strategi pemetaan yang baik untuk digunakan ketika kode Anda sebagian besar akan meminta entitas dari jenis daun tunggal dan tolok ukur Anda menunjukkan peningkatan dibandingkan dengan TPH.
Gunakan TPT hanya jika dibatasi untuk melakukannya oleh faktor eksternal.