Bagikan melalui


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.
    • Setter tidak perlu publik; dapat bersifat pribadi atau memiliki aksesibilitas lainnya.
    • Setter dapat berupa Init-only.
  • 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, 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 , atau bisa, jenis entitas. Ini berarti bahwa jenis TEntity:
    • 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 adalah PostsId dan TagsId.
    • Untuk banyak ke banyak arah unidirectional, properti kunci asing tanpa navigasi terkait diberi nama <principal entity type name><principal key name>.
  • 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 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 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: