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 digantikan oleh konfigurasi hubungan secara eksplisit menggunakan atribut pemetaan atau API pembuatan model.

Petunjuk / Saran

Kode di bawah ini dapat ditemukan di RelationshipConventions.cs.

Menemukan navigasi

Penemuan hubungan dimulai dengan menemukan navigasi antar jenis entitas.

Navigasi referensi

Properti dari jenis entitas ditemukan sebagai navigasi referensi ketika:

  • Properti ini bersifat publik.
  • Properti ini memiliki getter dan setter.
    • Setter tidak perlu bersifat terbuka; bisa bersifat pribadi atau memiliki aksesibilitas lainnya.
    • Setter dapat berupa Init-only.
  • Jenis properti adalah, atau dapat merupakan, jenis entitas. Ini berarti bahwa jenis
    • Harus merupakan tipe 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 tidak ditemukan sebagai navigasi referensi:

  • Blog.Id, karena int adalah jenis primitif yang dipetakan
  • Blog.Title, karena 'string' adalah jenis primitif yang dipetakan
  • Blog.Uri, karena Uri secara otomatis dikonversi ke jenis primitif yang dipetakan
  • Blog.ConsoleKeyInfo, karena ConsoleKeyInfo adalah jenis nilai C#
  • Blog.DefaultAuthor, karena properti tidak memiliki setter
  • Author.Id, karena Guid adalah jenis primitif yang dipetakan
  • Author.Name, karena 'string' adalah jenis primitif yang dipetakan
  • Author.BlogId, karena int 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 mana TEntity adalah, atau bisa merupakan, jenis entitas. Hal ini menandakan bahwa jenis TEntity:
    • Harus merupakan tipe 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 pasangan

Setelah navigasi dari, misalnya, jenis entitas A ke jenis entitas B ditemukan, langkah berikutnya adalah menentukan apakah navigasi tersebut memiliki arah inversi yang berlawanan—yaitu, dari jenis entitas B ke jenis entitas A. Jika inversi seperti itu ditemukan, kedua navigasi tersebut dipasangkan untuk membentuk satu hubungan dua arah yang saling terhubung.

Jenis hubungan ditentukan berdasarkan apakah navigasi dan inversinya berupa 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 satu-ke-banyak antara Blog dan Post ditemukan melalui memasangkan navigasi Blog.Posts 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 melalui pemasangan navigasi Blog.Author 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>();
}

Nota

Pasangan navigasi ini mungkin salah jika kedua navigasi mewakili dua hubungan searah yang berbeda. 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.

Nota

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 jenis yang sama dapat memiliki dua navigasi yang saling dipasangkan. 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 dianggap kompatibel jika mereka sama, atau jika jenis properti kunci asing adalah versi yang dapat di-null-kan 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 ujung dependen telah dikonfigurasi secara eksplisit menggunakan API untuk membangun model, dan kunci utama dependen tersebut kompatibel, maka kunci utama dependen juga akan digunakan sebagai kunci asing.

Petunjuk / Saran

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; }
}

Nota

Dalam kasus navigasi satu-ke-banyak, properti kunci asing harus berada pada jenis dengan navigasi referensi, karena entitas 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 navigasi referensi yang tidak berpasangan, maka hubungan dikonfigurasi sebagai satu-ke-banyak yang searah, dengan navigasi referensi pada akhir dependen.
  • Jika ada navigasi koleksi yang tidak berpasangan, maka hubungan dikonfigurasi sebagai satu-ke-banyak unidirectional, dengan navigasi koleksi sebagai pihak 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 yang bersifat dua arah, dengan properti kunci asing berada di sisi 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 sisi utama dari hubungan.
    • Tipe ini dibuat nullable secara default, menjadikan hubungan opsional secara default.
  • Jika terdapat navigasi pada ujung dependen, maka properti kunci asing bayangan dinamai menggunakan nama navigasi tersebut yang digabungkan dengan nama properti kunci utama atau alternatif.
  • Jika tidak ada navigasi pada sisi 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 penghapusan berurutan. Hubungan opsional dikonfigurasi agar tidak melakukan penghapusan berantai.

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 penghubung yang berisi pasangan kunci asing yang menunjuk ke kedua ujung dari hubungan banyak ke banyak. Pertimbangkan tipe entitas berikut, yang mana hubungan banyak ke banyak teridentifikasi melalui 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 adalah PostsId dan TagsId.
    • Untuk hubungan banyak-ke-banyak searah, properti kunci asing tanpa navigasi terkait diberi nama <principal entity type name><principal key name>.
  • Properti kunci asing tidak dapat bernilai null, mengharuskan kedua hubungan dengan entitas gabungan.
    • 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 dan TagsId.

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 yang sederhana dibuat sesuai dengan konvensi. Indeks yang sama dibuat untuk hubungan yang opsional dan yang 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. Contohnya:

CREATE INDEX "IX_Post_ContainingBlogId1_ContainingBlogId2" ON "Post" ("ContainingBlogId1", "ContainingBlogId2");

Nota

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