Konvensi untuk penemuan hubungan
EF Core menggunakan serangkaian konvensi saat menemukan dan membangun model berdasarkan kelas jenis entitas. Dokumen ini meringkas konvensi yang digunakan untuk menemukan dan mengonfigurasi hubungan antar jenis entitas.
Penting
Konvensi yang dijelaskan di sini dapat ditimpa oleh konfigurasi eksplisit hubungan menggunakan atribut pemetaan atau API pembuatan model.
Tip
Kode di bawah ini dapat ditemukan di RelationshipConventions.cs.
Menemukan navigasi
Penemuan hubungan dimulai dengan menemukan navigasi antar jenis entitas.
Navigasi referensi
Properti jenis entitas ditemukan sebagai navigasi referensi saat:
- Properti ini bersifat publik.
- Properti ini memiliki getter dan setter.
- Jenis properti adalah, atau bisa, jenis entitas. Ini berarti bahwa jenis
- Harus merupakan jenis referensi.
- Tidak boleh dikonfigurasi secara eksplisit sebagai jenis properti primitif.
- Tidak boleh dipetakan sebagai jenis properti primitif oleh penyedia database yang digunakan.
- Tidak boleh dikonversi secara otomatis ke jenis properti primitif yang dipetakan oleh penyedia database yang digunakan.
- Properti tidak statis.
- Properti bukan properti pengindeks.
Misalnya, pertimbangkan jenis entitas berikut:
public class Blog
{
// Not discovered as reference navigations:
public int Id { get; set; }
public string Title { get; set; } = null!;
public Uri? Uri { get; set; }
public ConsoleKeyInfo ConsoleKeyInfo { get; set; }
public Author DefaultAuthor => new() { Name = $"Author of the blog {Title}" };
// Discovered as a reference navigation:
public Author? Author { get; private set; }
}
public class Author
{
// Not discovered as reference navigations:
public Guid Id { get; set; }
public string Name { get; set; } = null!;
public int BlogId { get; set; }
// Discovered as a reference navigation:
public Blog Blog { get; init; } = null!;
}
Untuk jenis ini, Blog.Author
dan Author.Blog
ditemukan sebagai navigasi referensi. Di sisi lain, properti berikut ini tidak ditemukan sebagai navigasi referensi:
Blog.Id
, karenaint
adalah jenis primitif yang dipetakanBlog.Title
, karena 'string' adalah jenis primitif yang dipetakanBlog.Uri
, karenaUri
secara otomatis dikonversi ke jenis primitif yang dipetakanBlog.ConsoleKeyInfo
, karenaConsoleKeyInfo
adalah jenis nilai C#Blog.DefaultAuthor
, karena properti tidak memiliki setterAuthor.Id
, karenaGuid
adalah jenis primitif yang dipetakanAuthor.Name
, karena 'string' adalah jenis primitif yang dipetakanAuthor.BlogId
, karenaint
adalah jenis primitif yang dipetakan
Navigasi koleksi
Properti jenis entitas ditemukan sebagai navigasi koleksi saat:
- Properti ini bersifat publik.
- Properti ini memiliki getter. Navigasi koleksi dapat memiliki setter, tetapi ini tidak diperlukan.
- Jenis properti adalah atau mengimplementasikan
IEnumerable<TEntity>
, di manaTEntity
, atau bisa, jenis entitas. Ini berarti bahwa jenisTEntity
:- Harus merupakan jenis referensi.
- Tidak boleh dikonfigurasi secara eksplisit sebagai jenis properti primitif.
- Tidak boleh dipetakan sebagai jenis properti primitif oleh penyedia database yang digunakan.
- Tidak boleh dikonversi secara otomatis ke jenis properti primitif yang dipetakan oleh penyedia database yang digunakan.
- Properti tidak statis.
- Properti bukan properti pengindeks.
Misalnya, dalam kode berikut, keduanya Blog.Tags
dan Tag.Blogs
ditemukan sebagai navigasi koleksi:
public class Blog
{
public int Id { get; set; }
public List<Tag> Tags { get; set; } = null!;
}
public class Tag
{
public Guid Id { get; set; }
public IEnumerable<Blog> Blogs { get; } = new List<Blog>();
}
Navigasi pemasangan
Setelah navigasi berasal, misalnya, jenis entitas A ke jenis entitas B ditemukan, navigasi ini harus ditentukan berikutnya jika navigasi ini memiliki inversi ke arah yang berlawanan--yaitu, dari jenis entitas B ke jenis entitas A. Jika inversi seperti itu ditemukan, kedua navigasi dipasangkan bersama-sama untuk membentuk hubungan dua arah tunggal.
Jenis hubungan ditentukan oleh apakah navigasi dan inversinya adalah navigasi referensi atau koleksi. Khususnya:
- Jika satu navigasi adalah navigasi koleksi dan navigasi lainnya adalah navigasi referensi, maka hubungannya adalah satu-ke-banyak.
- Jika kedua navigasi adalah navigasi referensi, maka hubungannya adalah satu-ke-satu.
- Jika kedua navigasi adalah navigasi koleksi, maka hubungannya banyak ke banyak.
Penemuan masing-masing jenis hubungan ini ditunjukkan dalam contoh di bawah ini:
Hubungan tunggal ke banyak ditemukan antara Blog
dan Post
ditemukan dengan memasangkan Blog.Posts
navigasi dan Post.Blog
:
public class Blog
{
public int Id { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>();
}
public class Post
{
public int Id { get; set; }
public int? BlogId { get; set; }
public Blog? Blog { get; set; }
}
Hubungan satu-ke-satu ditemukan antara Blog
dan Author
ditemukan dengan memasangkan Blog.Author
navigasi dan Author.Blog
:
public class Blog
{
public int Id { get; set; }
public Author? Author { get; set; }
}
public class Author
{
public int Id { get; set; }
public int? BlogId { get; set; }
public Blog? Blog { get; set; }
}
Hubungan tunggal ke banyak ditemukan antara Post
dan Tag
ditemukan dengan memasangkan Post.Tags
navigasi dan Tag.Posts
:
public class Post
{
public int Id { get; set; }
public ICollection<Tag> Tags { get; } = new List<Tag>();
}
public class Tag
{
public int Id { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>();
}
Catatan
Pasangan navigasi ini mungkin salah jika kedua navigasi mewakili dua, berbeda, hubungan tidak langsung. Dalam hal ini, kedua hubungan harus dikonfigurasi secara eksplisit.
Pemasangan hubungan hanya berfungsi ketika ada satu hubungan antara dua jenis. Beberapa hubungan antara dua jenis harus dikonfigurasi secara eksplisit.
Catatan
Deskripsi di sini adalah dalam hal hubungan antara dua jenis yang berbeda. Namun, dimungkinkan bagi jenis yang sama untuk berada di kedua ujung hubungan, dan oleh karena itu untuk satu jenis untuk memiliki dua navigasi yang keduanya dipasangkan satu sama lain. Ini disebut hubungan referensi mandiri.
Menemukan properti kunci asing
Setelah navigasi untuk hubungan ditemukan atau dikonfigurasi secara eksplisit, navigasi ini digunakan untuk menemukan properti kunci asing yang sesuai untuk hubungan tersebut. Properti ditemukan sebagai kunci asing saat:
- Jenis properti kompatibel dengan kunci utama atau alternatif pada jenis entitas utama.
- Jenis kompatibel jika sama, atau jika jenis properti kunci asing adalah versi nullable dari jenis properti kunci utama atau alternatif.
- Nama properti cocok dengan salah satu konvensi penamaan untuk properti kunci asing. Konvensi penamaannya adalah:
<navigation property name><principal key property name>
<navigation property name>Id
<principal entity type name><principal key property name>
<principal entity type name>Id
- Selain itu, jika akhir dependen telah dikonfigurasi secara eksplisit menggunakan API pembuatan model, dan kunci primer dependen kompatibel, kunci primer dependen juga akan digunakan sebagai kunci asing.
Tip
Akhiran "Id" dapat memiliki casing apa pun.
Jenis entitas berikut menunjukkan contoh untuk masing-masing konvensi penamaan ini.
Post.TheBlogKey
ditemukan sebagai kunci asing karena cocok dengan pola <navigation property name><principal key property name>
:
public class Blog
{
public int Key { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>();
}
public class Post
{
public int Id { get; set; }
public int? TheBlogKey { get; set; }
public Blog? TheBlog { get; set; }
}
Post.TheBlogID
ditemukan sebagai kunci asing karena cocok dengan pola <navigation property name>Id
:
public class Blog
{
public int Key { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>();
}
public class Post
{
public int Id { get; set; }
public int? TheBlogID { get; set; }
public Blog? TheBlog { get; set; }
}
Post.BlogKey
ditemukan sebagai kunci asing karena cocok dengan pola <principal entity type name><principal key property name>
:
public class Blog
{
public int Key { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>();
}
public class Post
{
public int Id { get; set; }
public int? BlogKey { get; set; }
public Blog? TheBlog { get; set; }
}
Post.Blogid
ditemukan sebagai kunci asing karena cocok dengan pola <principal entity type name>Id
:
public class Blog
{
public int Key { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>();
}
public class Post
{
public int Id { get; set; }
public int? Blogid { get; set; }
public Blog? TheBlog { get; set; }
}
Catatan
Dalam kasus navigasi satu-ke-banyak, properti kunci asing harus berada pada jenis dengan navigasi referensi, karena ini akan menjadi entitas dependen. Dalam kasus hubungan satu-ke-satu, penemuan properti kunci asing digunakan untuk menentukan jenis mana yang mewakili akhir hubungan dependen. Jika tidak ada properti kunci asing yang ditemukan, maka akhir dependen harus dikonfigurasi menggunakan HasForeignKey
. Lihat Hubungan satu-ke-satu untuk contoh ini.
Aturan di atas juga berlaku untuk komposit kunci asing, di mana setiap properti komposit harus memiliki jenis yang kompatibel dengan properti yang sesuai dari kunci utama atau alternatif, dan setiap nama properti harus cocok dengan salah satu konvensi penamaan yang dijelaskan di atas.
Menentukan kardinalitas
EF menggunakan navigasi yang ditemukan dan properti kunci asing untuk menentukan kardinalitas hubungan bersama dengan ujung utama dan dependennya:
- Jika ada satu, navigasi referensi yang belum dipasang, maka hubungan dikonfigurasi sebagai satu-ke-banyak yang tidak langsung, dengan navigasi referensi pada akhir dependen.
- Jika ada satu, navigasi koleksi yang belum dipasang, maka hubungan dikonfigurasi sebagai satu-ke-banyak unidirectional, dengan navigasi koleksi di ujung utama.
- Jika ada navigasi referensi dan koleksi yang dipasangkan, maka hubungan dikonfigurasi sebagai dua arah satu ke banyak, dengan navigasi koleksi di ujung utama.
- Jika navigasi referensi dipasangkan dengan navigasi referensi lain, maka:
- Jika properti kunci asing ditemukan di satu sisi tetapi tidak yang lain, maka hubungan dikonfigurasi sebagai satu-ke-satu dua, dengan properti kunci asing pada ujung dependen.
- Jika tidak, sisi dependen tidak dapat ditentukan dan EF memberikan pengecualian yang menunjukkan bahwa dependen harus dikonfigurasi secara eksplisit.
- Jika navigasi koleksi dipasangkan dengan navigasi koleksi lain, maka hubungan dikonfigurasi sebagai banyak-ke-banyak dua arah.
Properti kunci asing bayangan
Jika EF telah menentukan akhir hubungan dependen tetapi tidak ada properti kunci asing yang ditemukan, maka EF akan membuat properti bayangan untuk mewakili kunci asing. Properti bayangan:
- Memiliki jenis properti kunci utama atau alternatif di akhir utama hubungan.
- Jenis ini dibuat nullable secara default, membuat hubungan opsional secara default.
- Jika ada navigasi pada ujung dependen, maka properti kunci asing bayangan diberi nama menggunakan nama navigasi ini digabungkan dengan nama properti kunci utama atau alternatif.
- Jika tidak ada navigasi pada ujung dependen, maka properti kunci asing bayangan dinamai menggunakan nama jenis entitas utama yang digabungkan dengan nama properti kunci utama atau alternatif.
Penghapusan Kaskade
Menurut konvensi, hubungan yang diperlukan dikonfigurasi untuk menghapus kaskade. Hubungan opsional dikonfigurasi untuk tidak menghapus kaskade.
Banyak-ke-banyak
Hubungan banyak ke banyak tidak memiliki ujung utama dan dependen, dan tidak ada ujung yang berisi properti kunci asing. Sebaliknya, hubungan banyak ke banyak menggunakan jenis entitas gabungan yang berisi pasangan kunci asing yang menunjuk ke salah satu akhir dari banyak ke banyak. Pertimbangkan jenis entitas berikut, di mana hubungan banyak ke banyak ditemukan berdasarkan konvensi:
public class Post
{
public int Id { get; set; }
public ICollection<Tag> Tags { get; } = new List<Tag>();
}
public class Tag
{
public int Id { get; set; }
public ICollection<Post> Posts { get; } = new List<Post>();
}
Konvensi yang digunakan dalam penemuan ini adalah:
- Jenis entitas gabungan diberi nama
<left entity type name><right entity type name>
. Jadi,PostTag
dalam contoh ini.- Tabel gabungan memiliki nama yang sama dengan jenis entitas gabungan.
- Jenis entitas gabungan diberi properti kunci asing untuk setiap arah hubungan. Ini diberi nama
<navigation name><principal key name>
. Jadi, dalam contoh ini, properti kunci asing adalahPostsId
danTagsId
.- Untuk banyak ke banyak arah unidirectional, properti kunci asing tanpa navigasi terkait diberi nama
<principal entity type name><principal key name>
.
- Untuk banyak ke banyak arah unidirectional, properti kunci asing tanpa navigasi terkait diberi nama
- Properti kunci asing tidak dapat diubah ke null, membuat kedua hubungan dengan entitas gabungan diperlukan.
- Konvensi penghapusan kaskade berarti bahwa hubungan ini akan dikonfigurasi untuk penghapusan kaskade.
- Jenis entitas gabungan dikonfigurasi dengan kunci primer komposit yang terdiri dari dua properti kunci asing. Jadi, dalam contoh ini, kunci primer terdiri dari
PostsId
danTagsId
.
Ini menghasilkan model EF berikut:
Model:
EntityType: Post
Properties:
Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
Skip navigations:
Tags (ICollection<Tag>) CollectionTag Inverse: Posts
Keys:
Id PK
EntityType: Tag
Properties:
Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
Skip navigations:
Posts (ICollection<Post>) CollectionPost Inverse: Tags
Keys:
Id PK
EntityType: PostTag (Dictionary<string, object>) CLR Type: Dictionary<string, object>
Properties:
PostsId (no field, int) Indexer Required PK FK AfterSave:Throw
TagsId (no field, int) Indexer Required PK FK Index AfterSave:Throw
Keys:
PostsId, TagsId PK
Foreign keys:
PostTag (Dictionary<string, object>) {'PostsId'} -> Post {'Id'} Cascade
PostTag (Dictionary<string, object>) {'TagsId'} -> Tag {'Id'} Cascade
Indexes:
TagsId
Dan diterjemahkan ke skema database berikut saat menggunakan SQLite:
CREATE TABLE "Posts" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_Posts" PRIMARY KEY AUTOINCREMENT);
CREATE TABLE "Tag" (
"Id" INTEGER NOT NULL CONSTRAINT "PK_Tag" PRIMARY KEY AUTOINCREMENT);
CREATE TABLE "PostTag" (
"PostsId" INTEGER NOT NULL,
"TagsId" INTEGER NOT NULL,
CONSTRAINT "PK_PostTag" PRIMARY KEY ("PostsId", "TagsId"),
CONSTRAINT "FK_PostTag_Posts_PostsId" FOREIGN KEY ("PostsId") REFERENCES "Posts" ("Id") ON DELETE CASCADE,
CONSTRAINT "FK_PostTag_Tag_TagsId" FOREIGN KEY ("TagsId") REFERENCES "Tag" ("Id") ON DELETE CASCADE);
CREATE INDEX "IX_PostTag_TagsId" ON "PostTag" ("TagsId");
Indeks
Menurut konvensi, EF membuat indeks database untuk properti atau properti kunci asing. Jenis indeks yang dibuat ditentukan oleh:
- Kardinalitas hubungan
- Apakah hubungan bersifat opsional atau diperlukan
- Jumlah properti yang membentuk kunci asing
Untuk hubungan satu ke banyak, indeks langsung dibuat oleh konvensi. Indeks yang sama dibuat untuk hubungan opsional dan diperlukan. Misalnya, pada SQLite:
CREATE INDEX "IX_Post_BlogId" ON "Post" ("BlogId");
Atau di SQL Server:
CREATE INDEX [IX_Post_BlogId] ON [Post] ([BlogId]);
Untuk hubungan satu-ke-satu yang diperlukan, indeks unik dibuat. Misalnya, pada SQLite:
CREATE UNIQUE INDEX "IX_Author_BlogId" ON "Author" ("BlogId");
Atau di SQL Sever:
CREATE UNIQUE INDEX [IX_Author_BlogId] ON [Author] ([BlogId]);
Untuk hubungan opsional satu-ke-satu, indeks yang dibuat di SQLite sama:
CREATE UNIQUE INDEX "IX_Author_BlogId" ON "Author" ("BlogId");
Namun, di SQL Server, IS NOT NULL
filter ditambahkan untuk menangani nilai kunci asing null dengan lebih baik. Contohnya:
CREATE UNIQUE INDEX [IX_Author_BlogId] ON [Author] ([BlogId]) WHERE [BlogId] IS NOT NULL;
Untuk kunci asing komposit, indeks dibuat yang mencakup semua kolom kunci asing. Contoh:
CREATE INDEX "IX_Post_ContainingBlogId1_ContainingBlogId2" ON "Post" ("ContainingBlogId1", "ContainingBlogId2");
Catatan
EF tidak membuat indeks untuk properti yang sudah dicakup oleh indeks yang ada atau batasan kunci utama.
Cara menghentikan pembuatan indeks EF untuk kunci asing
Indeks memiliki overhead, dan, seperti yang ditanyakan di sini, mungkin tidak selalu sesuai untuk membuatnya untuk semua kolom FK. Untuk mencapai hal ini, ForeignKeyIndexConvention
dapat dihapus saat membangun model:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Conventions.Remove(typeof(ForeignKeyIndexConvention));
}
Jika diinginkan, indeks masih dapat dibuat secara eksplisit untuk kolom kunci asing yang memang membutuhkannya.
Nama batasan kunci asing
Berdasarkan konvensi batasan kunci asing diberi nama FK_<dependent type name>_<principal type name>_<foreign key property name>
. Untuk kunci asing komposit, <foreign key property name>
menjadi daftar nama properti kunci asing yang dipisahkan garis bawah.
Sumber Daya Tambahan:
- Video .NET Data Community Standup pada konvensi model kustom.