Bagikan melalui


Mengubah Kunci dan Navigasi Asing

Gambaran umum kunci dan navigasi asing

Hubungan dalam model Entity Framework Core (EF Core) diwakili menggunakan kunci asing (FK). FK terdiri dari satu atau beberapa properti pada entitas dependen atau anak dalam hubungan. Entitas dependen/anak ini dikaitkan dengan entitas utama/induk tertentu ketika nilai properti kunci asing pada dependen/anak cocok dengan nilai properti kunci alternatif atau primer (PK) pada prinsipal/induk.

Kunci asing adalah cara yang baik untuk menyimpan dan memanipulasi hubungan dalam database, tetapi tidak terlalu ramah ketika bekerja dengan beberapa entitas terkait dalam kode aplikasi. Oleh karena itu, sebagian besar model EF Core juga melapisi "navigasi" atas representasi FK. Navigasi formulir referensi C#/.NET antara instans entitas yang mencerminkan asosiasi yang ditemukan dengan mencocokkan nilai kunci asing dengan nilai kunci primer atau alternatif.

Navigasi dapat digunakan di kedua sisi hubungan, hanya di satu sisi, atau tidak sama sekali, hanya menyisakan properti FK. Properti FK dapat disembunyikan dengan menjadikannya properti bayangan. Lihat Hubungan untuk informasi selengkapnya tentang hubungan pemodelan.

Tip

Dokumen ini mengasumsikan bahwa status entitas dan dasar-dasar pelacakan perubahan EF Core dipahami. Lihat Pelacakan Perubahan di EF Core untuk informasi selengkapnya tentang topik ini.

Tip

Anda dapat menjalankan dan men-debug ke semua kode dalam dokumen ini dengan mengunduh kode sampel dari GitHub.

Contoh model

Model berikut berisi empat jenis entitas dengan hubungan di antaranya. Komentar dalam kode menunjukkan properti mana yang merupakan kunci asing, kunci primer, dan navigasi.

public class Blog
{
    public int Id { get; set; } // Primary key
    public string Name { get; set; }

    public IList<Post> Posts { get; } = new List<Post>(); // Collection navigation
    public BlogAssets Assets { get; set; } // Reference navigation
}

public class BlogAssets
{
    public int Id { get; set; } // Primary key
    public byte[] Banner { get; set; }

    public int? BlogId { get; set; } // Foreign key
    public Blog Blog { get; set; } // Reference navigation
}

public class Post
{
    public int Id { get; set; } // Primary key
    public string Title { get; set; }
    public string Content { get; set; }

    public int? BlogId { get; set; } // Foreign key
    public Blog Blog { get; set; } // Reference navigation

    public IList<Tag> Tags { get; } = new List<Tag>(); // Skip collection navigation
}

public class Tag
{
    public int Id { get; set; } // Primary key
    public string Text { get; set; }

    public IList<Post> Posts { get; } = new List<Post>(); // Skip collection navigation
}

Tiga hubungan dalam model ini adalah:

  • Setiap blog dapat memiliki banyak postingan (satu-ke-banyak):
    • Blog adalah prinsipal/induk.
    • Post adalah dependen/anak. Ini berisi properti Post.BlogIdFK , nilai yang harus cocok dengan Blog.Id nilai PK blog terkait.
    • Post.Blog adalah navigasi referensi dari postingan ke blog terkait. Post.Blog adalah navigasi terbalik untuk Blog.Posts.
    • Blog.Posts adalah navigasi koleksi dari blog ke semua posting terkait. Blog.Posts adalah navigasi terbalik untuk Post.Blog.
  • Setiap blog dapat memiliki satu aset (satu-ke-satu):
    • Blog adalah prinsipal/induk.
    • BlogAssets adalah dependen/anak. Ini berisi properti BlogAssets.BlogIdFK , nilai yang harus cocok dengan Blog.Id nilai PK blog terkait.
    • BlogAssets.Blog adalah navigasi referensi dari aset ke blog terkait. BlogAssets.Blog adalah navigasi terbalik untuk Blog.Assets.
    • Blog.Assets adalah navigasi referensi dari blog ke aset terkait. Blog.Assets adalah navigasi terbalik untuk BlogAssets.Blog.
  • Setiap postingan dapat memiliki banyak tag dan setiap tag dapat memiliki banyak postingan (banyak ke banyak):
    • Hubungan banyak ke banyak adalah lapisan lebih lanjut dari dua hubungan satu-ke-banyak. Hubungan banyak ke banyak dibahas nanti dalam dokumen ini.
    • Post.Tags adalah navigasi koleksi dari postingan ke semua tag terkait. Post.Tags adalah navigasi terbalik untuk Tag.Posts.
    • Tag.Posts adalah navigasi koleksi dari tag ke semua postingan terkait. Tag.Posts adalah navigasi terbalik untuk Post.Tags.

Lihat Hubungan untuk informasi selengkapnya tentang cara memodelkan dan mengonfigurasi hubungan.

Perbaikan hubungan

EF Core menjaga navigasi selaras dengan nilai kunci asing dan sebaliknya. Artinya, jika nilai kunci asing berubah singa sehingga sekarang mengacu pada entitas utama/induk yang berbeda, maka navigasi diperbarui untuk mencerminkan perubahan ini. Demikian juga, jika navigasi diubah, maka nilai kunci asing entitas yang terlibat diperbarui untuk mencerminkan perubahan ini. Ini disebut "perbaikan hubungan".

Perbaikan menurut kueri

Perbaikan pertama kali terjadi ketika entitas dikueri dari database. Database hanya memiliki nilai kunci asing, jadi ketika EF Core membuat instans entitas dari database, database tersebut menggunakan nilai kunci asing untuk mengatur navigasi referensi dan menambahkan entitas ke navigasi pengumpulan yang sesuai. Misalnya, pertimbangkan kueri untuk blog dan posting dan aset terkaitnya:

using var context = new BlogsContext();

var blogs = context.Blogs
    .Include(e => e.Posts)
    .Include(e => e.Assets)
    .ToList();

Console.WriteLine(context.ChangeTracker.DebugView.LongView);

Untuk setiap blog, EF Core akan terlebih dahulu membuat Blog instans. Kemudian, karena setiap posting dimuat dari database, navigasi referensinya Post.Blog diatur untuk menunjuk ke blog terkait. Demikian juga, postingan ditambahkan ke Blog.Posts navigasi koleksi. Hal yang sama terjadi dengan BlogAssets, kecuali dalam hal ini kedua navigasi adalah referensi. Navigasi Blog.Assets diatur untuk menunjuk ke instans aset, dan BlogAsserts.Blog navigasi diatur untuk menunjuk ke instans blog.

Melihat tampilan debug pelacak perubahan setelah kueri ini menunjukkan dua blog, masing-masing dengan satu aset dan dua postingan yang dilacak:

Blog {Id: 1} Unchanged
  Id: 1 PK
  Name: '.NET Blog'
  Assets: {Id: 1}
  Posts: [{Id: 1}, {Id: 2}]
Blog {Id: 2} Unchanged
  Id: 2 PK
  Name: 'Visual Studio Blog'
  Assets: {Id: 2}
  Posts: [{Id: 3}, {Id: 4}]
BlogAssets {Id: 1} Unchanged
  Id: 1 PK
  Banner: <null>
  BlogId: 1 FK
  Blog: {Id: 1}
BlogAssets {Id: 2} Unchanged
  Id: 2 PK
  Banner: <null>
  BlogId: 2 FK
  Blog: {Id: 2}
Post {Id: 1} Unchanged
  Id: 1 PK
  BlogId: 1 FK
  Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
  Title: 'Announcing the Release of EF Core 5.0'
  Blog: {Id: 1}
  Tags: []
Post {Id: 2} Unchanged
  Id: 2 PK
  BlogId: 1 FK
  Content: 'F# 5 is the latest version of F#, the functional programming...'
  Title: 'Announcing F# 5'
  Blog: {Id: 1}
  Tags: []
Post {Id: 3} Unchanged
  Id: 3 PK
  BlogId: 2 FK
  Content: 'If you are focused on squeezing out the last bits of perform...'
  Title: 'Disassembly improvements for optimized managed debugging'
  Blog: {Id: 2}
  Tags: []
Post {Id: 4} Unchanged
  Id: 4 PK
  BlogId: 2 FK
  Content: 'Examine when database queries were executed and measure how ...'
  Title: 'Database Profiling with Visual Studio'
  Blog: {Id: 2}
  Tags: []

Tampilan debug memperlihatkan nilai kunci dan navigasi. Navigasi diperlihatkan menggunakan nilai kunci utama entitas terkait. Misalnya, Posts: [{Id: 1}, {Id: 2}] dalam output di atas menunjukkan bahwa Blog.Posts navigasi koleksi berisi dua posting terkait dengan kunci primer 1 dan 2 masing-masing. Demikian pula, untuk setiap posting yang terkait dengan blog pertama, Blog: {Id: 1} baris menunjukkan bahwa Post.Blog navigasi mereferensikan Blog dengan kunci utama 1.

Perbaikan ke entitas yang dilacak secara lokal

Perbaikan hubungan juga terjadi antara entitas yang dikembalikan dari kueri pelacakan dan entitas yang sudah dilacak oleh DbContext. Misalnya, pertimbangkan untuk menjalankan tiga kueri terpisah untuk blog, posting, dan aset:

using var context = new BlogsContext();

var blogs = context.Blogs.ToList();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);

var assets = context.Assets.ToList();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);

var posts = context.Posts.ToList();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);

Melihat lagi tampilan debug, setelah kueri pertama hanya dua blog yang dilacak:

Blog {Id: 1} Unchanged
  Id: 1 PK
  Name: '.NET Blog'
  Assets: <null>
  Posts: []
Blog {Id: 2} Unchanged
  Id: 2 PK
  Name: 'Visual Studio Blog'
  Assets: <null>
  Posts: []

Navigasi Blog.Assets referensi null, dan Blog.Posts navigasi koleksi kosong karena tidak ada entitas terkait yang saat ini sedang dilacak oleh konteks.

Setelah kueri kedua, Blogs.Assets navigasi referensi telah diperbaiki hingga menunjuk ke instans yang baru dilacak BlogAsset . Demikian juga, BlogAssets.Blog navigasi referensi diatur untuk menunjuk ke instans yang sudah dilacak Blog yang sesuai.

Blog {Id: 1} Unchanged
  Id: 1 PK
  Name: '.NET Blog'
  Assets: {Id: 1}
  Posts: []
Blog {Id: 2} Unchanged
  Id: 2 PK
  Name: 'Visual Studio Blog'
  Assets: {Id: 2}
  Posts: []
BlogAssets {Id: 1} Unchanged
  Id: 1 PK
  Banner: <null>
  BlogId: 1 FK
  Blog: {Id: 1}
BlogAssets {Id: 2} Unchanged
  Id: 2 PK
  Banner: <null>
  BlogId: 2 FK
  Blog: {Id: 2}

Terakhir, setelah kueri ketiga, Blog.Posts navigasi koleksi sekarang berisi semua posting terkait, dan Post.Blog referensi menunjuk ke instans yang sesuai Blog :

Blog {Id: 1} Unchanged
  Id: 1 PK
  Name: '.NET Blog'
  Assets: {Id: 1}
  Posts: [{Id: 1}, {Id: 2}]
Blog {Id: 2} Unchanged
  Id: 2 PK
  Name: 'Visual Studio Blog'
  Assets: {Id: 2}
  Posts: [{Id: 3}, {Id: 4}]
BlogAssets {Id: 1} Unchanged
  Id: 1 PK
  Banner: <null>
  BlogId: 1 FK
  Blog: {Id: 1}
BlogAssets {Id: 2} Unchanged
  Id: 2 PK
  Banner: <null>
  BlogId: 2 FK
  Blog: {Id: 2}
Post {Id: 1} Unchanged
  Id: 1 PK
  BlogId: 1 FK
  Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
  Title: 'Announcing the Release of EF Core 5.0'
  Blog: {Id: 1}
  Tags: []
Post {Id: 2} Unchanged
  Id: 2 PK
  BlogId: 1 FK
  Content: 'F# 5 is the latest version of F#, the functional programming...'
  Title: 'Announcing F# 5'
  Blog: {Id: 1}
  Tags: []
Post {Id: 3} Unchanged
  Id: 3 PK
  BlogId: 2 FK
  Content: 'If you are focused on squeezing out the last bits of perform...'
  Title: 'Disassembly improvements for optimized managed debugging'
  Blog: {Id: 2}
  Tags: []
Post {Id: 4} Unchanged
  Id: 4 PK
  BlogId: 2 FK
  Content: 'Examine when database queries were executed and measure how ...'
  Title: 'Database Profiling with Visual Studio'
  Blog: {Id: 2}
  Tags: []

Ini adalah status akhir yang sama seperti yang dicapai dengan kueri tunggal asli, karena EF Core memperbaiki navigasi saat entitas dilacak, bahkan ketika berasal dari beberapa kueri yang berbeda.

Catatan

Perbaikan tidak pernah menyebabkan lebih banyak data dikembalikan dari database. Ini hanya menyambungkan entitas yang sudah dikembalikan oleh kueri atau sudah dilacak oleh DbContext. Lihat Resolusi Identitas di EF Core untuk informasi tentang menangani duplikat saat menserialisasikan entitas.

Mengubah hubungan menggunakan navigasi

Cara term mudah untuk mengubah hubungan antara dua entitas adalah dengan memanipulasi navigasi, sambil meninggalkan EF Core untuk memperbaiki navigasi terbalik dan nilai FK dengan tepat. Hal ini dapat dilakukan dengan:

  • Menambahkan atau menghapus entitas dari navigasi koleksi.
  • Mengubah navigasi referensi untuk menunjuk ke entitas lain, atau mengaturnya ke null.

Menambahkan atau menghapus dari navigasi koleksi

Misalnya, mari kita pindahkan salah satu postingan dari blog Visual Studio ke blog .NET. Ini membutuhkan pemuatan blog dan posting pertama, lalu memindahkan postingan dari koleksi navigasi di satu blog ke koleksi navigasi di blog lain:

using var context = new BlogsContext();

var dotNetBlog = context.Blogs.Include(e => e.Posts).Single(e => e.Name == ".NET Blog");
var vsBlog = context.Blogs.Include(e => e.Posts).Single(e => e.Name == "Visual Studio Blog");

Console.WriteLine(context.ChangeTracker.DebugView.LongView);

var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
vsBlog.Posts.Remove(post);
dotNetBlog.Posts.Add(post);

context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);

context.SaveChanges();

Tip

Panggilan ke ChangeTracker.DetectChanges() diperlukan di sini karena mengakses tampilan debug tidak menyebabkan deteksi perubahan otomatis.

Ini adalah tampilan debug yang dicetak setelah menjalankan kode di atas:

Blog {Id: 1} Unchanged
  Id: 1 PK
  Name: '.NET Blog'
  Assets: <null>
  Posts: [{Id: 1}, {Id: 2}, {Id: 3}]
Blog {Id: 2} Unchanged
  Id: 2 PK
  Name: 'Visual Studio Blog'
  Assets: <null>
  Posts: [{Id: 4}]
Post {Id: 1} Unchanged
  Id: 1 PK
  BlogId: 1 FK
  Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
  Title: 'Announcing the Release of EF Core 5.0'
  Blog: {Id: 1}
  Tags: []
Post {Id: 2} Unchanged
  Id: 2 PK
  BlogId: 1 FK
  Content: 'F# 5 is the latest version of F#, the functional programming...'
  Title: 'Announcing F# 5'
  Blog: {Id: 1}
  Tags: []
Post {Id: 3} Modified
  Id: 3 PK
  BlogId: 1 FK Modified Originally 2
  Content: 'If you are focused on squeezing out the last bits of perform...'
  Title: 'Disassembly improvements for optimized managed debugging'
  Blog: {Id: 1}
  Tags: []
Post {Id: 4} Unchanged
  Id: 4 PK
  BlogId: 2 FK
  Content: 'Examine when database queries were executed and measure how ...'
  Title: 'Database Profiling with Visual Studio'
  Blog: {Id: 2}
  Tags: []

Navigasi Blog.Posts di Blog .NET sekarang memiliki tiga posting (Posts: [{Id: 1}, {Id: 2}, {Id: 3}]). Demikian juga, Blog.Posts navigasi di blog Visual Studio hanya memiliki satu posting (Posts: [{Id: 4}]). Hal ini diharapkan karena kode secara eksplisit mengubah koleksi ini.

Lebih menarik lagi, meskipun kode tidak secara eksplisit mengubah Post.Blog navigasi, kode tersebut telah diperbaiki untuk menunjuk ke blog Visual Studio (Blog: {Id: 1}). Selain itu Post.BlogId , nilai kunci asing telah diperbarui agar sesuai dengan nilai kunci utama blog .NET. Perubahan ini pada nilai FK di kemudian bertahan ke database saat SaveChanges dipanggil:

-- Executed DbCommand (0ms) [Parameters=[@p1='3' (DbType = String), @p0='1' (Nullable = true) (DbType = String)], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "BlogId" = @p0
WHERE "Id" = @p1;
SELECT changes();

Mengubah navigasi referensi

Dalam contoh sebelumnya, posting dipindahkan dari satu blog ke blog lain dengan memanipulasi navigasi koleksi posting di setiap blog. Hal yang sama dapat dicapai dengan mengubah Post.Blog navigasi referensi untuk menunjuk ke blog baru. Contohnya:

var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
post.Blog = dotNetBlog;

Tampilan debug setelah perubahan ini sama persis seperti pada contoh sebelumnya. Ini karena EF Core mendeteksi perubahan navigasi referensi lalu memperbaiki navigasi koleksi dan nilai FK agar cocok.

Mengubah hubungan menggunakan nilai kunci asing

Di bagian sebelumnya, hubungan dimanipulasi oleh navigasi yang meninggalkan nilai kunci asing untuk diperbarui secara otomatis. Ini adalah cara yang disarankan untuk memanipulasi hubungan di EF Core. Namun, dimungkinkan juga untuk memanipulasi nilai FK secara langsung. Misalnya, kita dapat memindahkan postingan dari satu blog ke blog lain dengan mengubah nilai kunci asing Post.BlogId :

var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
post.BlogId = dotNetBlog.Id;

Perhatikan bagaimana ini sangat mirip dengan mengubah navigasi referensi, seperti yang ditunjukkan pada contoh sebelumnya.

Tampilan debug setelah perubahan ini lagi sama persis seperti kasus untuk dua contoh sebelumnya. Ini karena EF Core mendeteksi perubahan nilai FK lalu memperbaiki navigasi referensi dan koleksi agar sesuai.

Tip

Jangan menulis kode untuk memanipulasi semua navigasi dan nilai FK setiap kali hubungan berubah. Kode tersebut lebih rumit dan harus memastikan perubahan yang konsisten pada kunci asing dan navigasi dalam setiap kasus. Jika memungkinkan, cukup manipulasi satu navigasi, atau mungkin kedua navigasi. Jika diperlukan, cukup manipulasi nilai FK. Hindari memanipulasi navigasi dan nilai FK.

Perbaikan untuk entitas yang ditambahkan atau dihapus

Menambahkan ke navigasi koleksi

EF Core melakukan tindakan berikut ketika mendeteksi bahwa entitas dependen/anak baru telah ditambahkan ke navigasi koleksi:

  • Jika entitas tidak dilacak, maka entitas tersebut dilacak. (Entitas biasanya akan berada dalam status Added . Namun, jika jenis entitas dikonfigurasi untuk menggunakan kunci yang dihasilkan dan nilai kunci utama diatur, maka entitas dilacak dalam Unchanged status .)
  • Jika entitas dikaitkan dengan prinsipal/induk yang berbeda, maka hubungan tersebut terputus.
  • Entitas menjadi terkait dengan prinsipal/induk yang memiliki navigasi koleksi.
  • Navigasi dan nilai kunci asing diperbaiki untuk semua entitas yang terlibat.

Berdasarkan ini kita dapat melihat bahwa untuk memindahkan posting dari satu blog ke blog lain, kita tidak benar-benar perlu menghapusnya dari navigasi koleksi lama sebelum menambahkannya ke yang baru. Jadi kode dari contoh di atas dapat diubah dari:

var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
vsBlog.Posts.Remove(post);
dotNetBlog.Posts.Add(post);

Kepada:

var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
dotNetBlog.Posts.Add(post);

EF Core melihat bahwa postingan telah ditambahkan ke blog baru dan secara otomatis menghapusnya dari koleksi di blog pertama.

Menghapus dari navigasi koleksi

Menghapus entitas dependen/anak dari navigasi pengumpulan prinsipal/induk menyebabkan pemisahan hubungan dengan prinsipal/induk tersebut. Apa yang terjadi selanjutnya tergantung pada apakah hubungan bersifat opsional atau diperlukan.

Hubungan opsional

Secara default untuk hubungan opsional, nilai kunci asing diatur ke null. Ini berarti bahwa dependen/anak tidak lagi terkait dengan prinsipal/induk apa pun . Misalnya, mari kita muat blog dan postingan lalu hapus salah satu postingan dari Blog.Posts navigasi koleksi:

var post = dotNetBlog.Posts.Single(e => e.Title == "Announcing F# 5");
dotNetBlog.Posts.Remove(post);

Melihat tampilan debug pelacakan perubahan setelah perubahan ini menunjukkan bahwa:

  • Post.BlogId FK telah diatur ke null (BlogId: <null> FK Modified Originally 1)
  • Navigasi Post.Blog referensi telah diatur ke null (Blog: <null>)
  • Postingan telah dihapus dari Blog.Posts navigasi koleksi (Posts: [{Id: 1}])
Blog {Id: 1} Unchanged
  Id: 1 PK
  Name: '.NET Blog'
  Assets: <null>
  Posts: [{Id: 1}]
Post {Id: 1} Unchanged
  Id: 1 PK
  BlogId: 1 FK
  Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
  Title: 'Announcing the Release of EF Core 5.0'
  Blog: {Id: 1}
  Tags: []
Post {Id: 2} Modified
  Id: 2 PK
  BlogId: <null> FK Modified Originally 1
  Content: 'F# 5 is the latest version of F#, the functional programming...'
  Title: 'Announcing F# 5'
  Blog: <null>
  Tags: []

Perhatikan bahwa postingan tidak ditandai sebagai Deleted. Ini ditandai sebagai Modified sehingga nilai FK dalam database akan diatur ke null saat SaveChanges dipanggil.

Hubungan yang diperlukan

Mengatur nilai FK ke null tidak diperbolehkan (dan biasanya tidak dimungkinkan) untuk hubungan yang diperlukan. Oleh karena itu, memutuskan hubungan yang diperlukan berarti bahwa entitas dependen/anak harus di-induk ulang ke prinsipal/induk baru, atau dihapus dari database saat SaveChanges dipanggil untuk menghindari pelanggaran batasan referensial. Ini dikenal sebagai "menghapus yatim piatu", dan merupakan perilaku default di EF Core untuk hubungan yang diperlukan.

Misalnya, mari kita ubah hubungan antara blog dan postingan menjadi diperlukan lalu jalankan kode yang sama seperti dalam contoh sebelumnya:

var post = dotNetBlog.Posts.Single(e => e.Title == "Announcing F# 5");
dotNetBlog.Posts.Remove(post);

Melihat tampilan debug setelah perubahan ini menunjukkan bahwa:

  • Postingan telah ditandai sedih Deleted sehingga akan dihapus dari database saat SaveChanges dipanggil.
  • Navigasi Post.Blog referensi telah diatur ke null (Blog: <null>).
  • Postingan telah dihapus dari Blog.Posts navigasi koleksi (Posts: [{Id: 1}]).
Blog {Id: 1} Unchanged
  Id: 1 PK
  Name: '.NET Blog'
  Assets: <null>
  Posts: [{Id: 1}]
Post {Id: 1} Unchanged
  Id: 1 PK
  BlogId: 1 FK
  Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
  Title: 'Announcing the Release of EF Core 5.0'
  Blog: {Id: 1}
  Tags: []
Post {Id: 2} Deleted
  Id: 2 PK
  BlogId: 1 FK
  Content: 'F# 5 is the latest version of F#, the functional programming...'
  Title: 'Announcing F# 5'
  Blog: <null>
  Tags: []

Perhatikan bahwa Post.BlogId tetap tidak berubah karena untuk hubungan yang diperlukan tidak dapat diatur ke null.

Memanggil SaveChanges mengakibatkan postingan tanpa intim dihapus:

-- Executed DbCommand (0ms) [Parameters=[@p0='2' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Posts"
WHERE "Id" = @p0;
SELECT changes();

Menghapus waktu yatim piatu dan mengasuh ulang

Secara default, menandai yatim piatu seperti Deleted yang terjadi segera setelah perubahan hubungan terdeteksi. Namun, proses ini dapat ditunda sampai SaveChanges benar-benar dipanggil. Ini dapat berguna untuk menghindari pembuatan yatim piatu entitas yang telah dihapus dari satu prinsipal/induk, tetapi akan di-induk ulang dengan prinsipal/induk baru sebelum SaveChanges dipanggil. ChangeTracker.DeleteOrphansTiming digunakan untuk mengatur waktu ini. Contohnya:

context.ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges;

var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
vsBlog.Posts.Remove(post);

context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);

dotNetBlog.Posts.Add(post);

context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);

context.SaveChanges();

Setelah menghapus postingan dari koleksi pertama, objek tidak ditandai seperti Deleted pada contoh sebelumnya. Sebaliknya, EF Core melacak bahwa hubungan terputus meskipun ini adalah hubungan yang diperlukan. (Nilai FK dianggap null oleh EF Core meskipun tidak benar-benar null karena jenisnya tidak dapat diubah ke null. Ini dikenal sebagai "konseptual null".)

Post {Id: 3} Modified
  Id: 3 PK
  BlogId: <null> FK Modified Originally 2
  Content: 'If you are focused on squeezing out the last bits of perform...'
  Title: 'Disassembly improvements for optimized managed debugging'
  Blog: <null>
  Tags: []

Memanggil SaveChanges saat ini akan mengakibatkan postingan tanpa intim dihapus. Namun, jika seperti dalam contoh di atas, posting dikaitkan dengan blog baru sebelum SaveChanges dipanggil, maka itu akan diperbaiki dengan tepat ke blog baru itu dan tidak lagi dianggap sebagai yatim piatu:

Post {Id: 3} Modified
  Id: 3 PK
  BlogId: 1 FK Modified Originally 2
  Content: 'If you are focused on squeezing out the last bits of perform...'
  Title: 'Disassembly improvements for optimized managed debugging'
  Blog: {Id: 1}
  Tags: []

SaveChanges yang dipanggil pada saat ini akan memperbarui postingan dalam database daripada menghapusnya.

Dimungkinkan juga untuk mematikan penghapusan otomatis yatim piatu. Ini akan menghasilkan pengecualian jika SaveChanges dipanggil saat yatim piatu sedang dilacak. Misalnya, kode ini:

var dotNetBlog = context.Blogs.Include(e => e.Posts).Single(e => e.Name == ".NET Blog");

context.ChangeTracker.DeleteOrphansTiming = CascadeTiming.Never;

var post = dotNetBlog.Posts.Single(e => e.Title == "Announcing F# 5");
dotNetBlog.Posts.Remove(post);

context.SaveChanges(); // Throws

Akan melemparkan pengecualian ini:

System.InvalidOperationException: Hubungan antara entitas 'Blog' dan 'Posting' dengan nilai kunci '{BlogId: 1}' telah terputus, tetapi hubungan ditandai sebagai diperlukan atau secara implisit diperlukan karena kunci asing tidak dapat diubah ke null. Jika entitas dependen/anak harus dihapus saat hubungan yang diperlukan terputus, konfigurasikan hubungan untuk menggunakan penghapusan bertingkat.

Penghapusan yatim piatu, serta penghapusan kaskade, dapat dipaksa kapan saja dengan memanggil ChangeTracker.CascadeChanges(). Menggabungkan ini dengan mengatur pengaturan waktu hapus yatim piatu ke Never akan memastikan yatim piatu tidak pernah dihapus kecuali EF Core secara eksplisit diinstruksikan untuk melakukannya.

Mengubah navigasi referensi

Mengubah navigasi referensi hubungan satu ke banyak memiliki efek yang sama dengan mengubah navigasi koleksi di akhir hubungan lainnya. Mengatur navigasi referensi dependen/turunan ke null setara dengan menghapus entitas dari navigasi kumpulan prinsipal/induk. Semua perbaikan dan perubahan database terjadi seperti yang dijelaskan di bagian sebelumnya, termasuk menjadikan entitas sebagai yatim piatu jika hubungan diperlukan.

Hubungan satu-ke-satu opsional

Untuk hubungan satu-ke-satu, mengubah navigasi referensi menyebabkan hubungan sebelumnya terputus. Untuk hubungan opsional, ini berarti bahwa nilai FK pada dependen/anak terkait sebelumnya diatur ke null. Contohnya:

using var context = new BlogsContext();

var dotNetBlog = context.Blogs.Include(e => e.Assets).Single(e => e.Name == ".NET Blog");
dotNetBlog.Assets = new BlogAssets();

context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);

context.SaveChanges();

Tampilan debug sebelum memanggil SaveChanges menunjukkan bahwa aset baru telah menggantikan aset yang ada, yang sekarang ditandai sebagai Modified dengan nilai FK null BlogAssets.BlogId :

Blog {Id: 1} Unchanged
  Id: 1 PK
  Name: '.NET Blog'
  Assets: {Id: -2147482629}
  Posts: []
BlogAssets {Id: -2147482629} Added
  Id: -2147482629 PK Temporary
  Banner: <null>
  BlogId: 1 FK
  Blog: {Id: 1}
BlogAssets {Id: 1} Modified
  Id: 1 PK
  Banner: <null>
  BlogId: <null> FK Modified Originally 1
  Blog: <null>

Ini menghasilkan pembaruan dan penyisipan saat SaveChanges dipanggil:

-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p0=NULL], CommandType='Text', CommandTimeout='30']
UPDATE "Assets" SET "BlogId" = @p0
WHERE "Id" = @p1;
SELECT changes();

-- Executed DbCommand (0ms) [Parameters=[@p2=NULL, @p3='1' (Nullable = true) (DbType = String)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Assets" ("Banner", "BlogId")
VALUES (@p2, @p3);
SELECT "Id"
FROM "Assets"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();

Hubungan satu-ke-satu yang diperlukan

Menjalankan kode yang sama seperti dalam contoh sebelumnya, tetapi kali ini dengan hubungan satu-ke-satu yang diperlukan, menunjukkan bahwa yang sebelumnya terkait BlogAssets sekarang ditandai sebagai Deleted, karena menjadi yatim piatu ketika yang baru BlogAssets mengambil tempatnya:

Blog {Id: 1} Unchanged
  Id: 1 PK
  Name: '.NET Blog'
  Assets: {Id: -2147482639}
  Posts: []
BlogAssets {Id: -2147482639} Added
  Id: -2147482639 PK Temporary
  Banner: <null>
  BlogId: 1 FK
  Blog: {Id: 1}
BlogAssets {Id: 1} Deleted
  Id: 1 PK
  Banner: <null>
  BlogId: 1 FK
  Blog: <null>

Ini kemudian menghasilkan penghapusan dan penyisipan saat SaveChanges dipanggil:

-- Executed DbCommand (0ms) [Parameters=[@p0='1' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Assets"
WHERE "Id" = @p0;
SELECT changes();

-- Executed DbCommand (0ms) [Parameters=[@p1=NULL, @p2='1' (DbType = String)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Assets" ("Banner", "BlogId")
VALUES (@p1, @p2);
SELECT "Id"
FROM "Assets"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();

Waktu menandai yatim piatu sebagai dihapus dapat diubah dengan cara yang sama seperti yang ditunjukkan untuk navigasi koleksi dan memiliki efek yang sama.

Menghapus entitas

Hubungan opsional

Ketika entitas ditandai sebagai Deleted, misalnya dengan memanggil DbContext.Remove, maka referensi ke entitas yang dihapus dihapus dari navigasi entitas lain. Untuk hubungan opsional, nilai FK dalam entitas dependen diatur ke null.

Misalnya, mari kita tandai blog Visual Studio sebagai Deleted:

using var context = new BlogsContext();

var vsBlog = context.Blogs
    .Include(e => e.Posts)
    .Include(e => e.Assets)
    .Single(e => e.Name == "Visual Studio Blog");

context.Remove(vsBlog);

Console.WriteLine(context.ChangeTracker.DebugView.LongView);

context.SaveChanges();

Melihat tampilan debug pelacak perubahan sebelum memanggil SaveChanges menunjukkan:

Blog {Id: 2} Deleted
  Id: 2 PK
  Name: 'Visual Studio Blog'
  Assets: {Id: 2}
  Posts: [{Id: 3}, {Id: 4}]
BlogAssets {Id: 2} Modified
  Id: 2 PK
  Banner: <null>
  BlogId: <null> FK Modified Originally 2
  Blog: <null>
Post {Id: 3} Modified
  Id: 3 PK
  BlogId: <null> FK Modified Originally 2
  Content: 'If you are focused on squeezing out the last bits of perform...'
  Title: 'Disassembly improvements for optimized managed debugging'
  Blog: <null>
  Tags: []
Post {Id: 4} Modified
  Id: 4 PK
  BlogId: <null> FK Modified Originally 2
  Content: 'Examine when database queries were executed and measure how ...'
  Title: 'Database Profiling with Visual Studio'
  Blog: <null>
  Tags: []

Perhatikan bahwa:

  • Blog ditandai sebagai Deleted.
  • Aset yang terkait dengan blog yang dihapus memiliki nilai FK null (BlogId: <null> FK Modified Originally 2) dan navigasi referensi null (Blog: <null>)
  • Setiap postingan yang terkait dengan blog yang dihapus memiliki nilai FK null (BlogId: <null> FK Modified Originally 2) dan navigasi referensi null (Blog: <null>)

Hubungan yang diperlukan

Perilaku perbaikan untuk hubungan yang diperlukan sama dengan untuk hubungan opsional kecuali bahwa entitas dependen/anak ditandai karena Deleted tidak dapat ada tanpa prinsipal/induk dan harus dihapus dari database ketika SaveChanges dipanggil untuk menghindari pengecualian batasan referensial. Ini dikenal sebagai "penghapusan kaskade", dan merupakan perilaku default di EF Core untuk hubungan yang diperlukan. Misalnya, menjalankan kode yang sama seperti pada contoh sebelumnya tetapi dengan hubungan yang diperlukan menghasilkan tampilan debug berikut sebelum SaveChanges dipanggil:

Blog {Id: 2} Deleted
  Id: 2 PK
  Name: 'Visual Studio Blog'
  Assets: {Id: 2}
  Posts: [{Id: 3}, {Id: 4}]
BlogAssets {Id: 2} Deleted
  Id: 2 PK
  Banner: <null>
  BlogId: 2 FK
  Blog: {Id: 2}
Post {Id: 3} Deleted
  Id: 3 PK
  BlogId: 2 FK
  Content: 'If you are focused on squeezing out the last bits of perform...'
  Title: 'Disassembly improvements for optimized managed debugging'
  Blog: {Id: 2}
  Tags: []
Post {Id: 4} Deleted
  Id: 4 PK
  BlogId: 2 FK
  Content: 'Examine when database queries were executed and measure how ...'
  Title: 'Database Profiling with Visual Studio'
  Blog: {Id: 2}
  Tags: []

Seperti yang diharapkan, dependen/anak sekarang ditandai sebagai Deleted. Namun, perhatikan bahwa navigasi pada entitas yang dihapus tidak berubah. Ini mungkin tampak aneh, tetapi menghindari penghancuran sepenuhnya grafik entitas yang dihapus dengan menghapus semua navigasi. Artinya, blog, aset, dan postingan masih membentuk grafik entitas bahkan setelah dihapus. Ini membuatnya jauh lebih mudah untuk menghapus grafik entitas daripada yang terjadi di EF6 di mana grafik dihancurkan.

Waktu penghapusan berskala dan induk ulang

Secara default, penghapusan kaskade terjadi segera setelah induk/utama ditandai sebagai Deleted. Ini sama dengan untuk menghapus anak yatim, seperti yang dijelaskan sebelumnya. Seperti halnya menghapus yatim piatu, proses ini dapat ditunda sampai SaveChanges dipanggil, atau bahkan dinonaktifkan sepenuhnya, dengan mengatur ChangeTracker.CascadeDeleteTiming dengan tepat. Ini berguna dengan cara yang sama seperti untuk menghapus anak yatim, termasuk untuk mengasuh kembali anak/dependen setelah penghapusan prinsipal/induk.

Penghapusan kaskade, serta menghapus yatim piatu, dapat dipaksa kapan saja dengan memanggil ChangeTracker.CascadeChanges(). Menggabungkan ini dengan mengatur pengaturan waktu penghapusan kaskade untuk Never akan memastikan penghapusan kaskade tidak pernah terjadi kecuali EF Core secara eksplisit diinstruksikan untuk melakukannya.

Tip

Penghapusan kaskade dan penghapusan yatim piatu terkait erat. Keduanya mengakibatkan penghapusan entitas dependen/anak ketika hubungan dengan prinsipal/induk yang diperlukan terputus. Untuk penghapusan berjenjang, pemisahan ini terjadi karena prinsipal/induk itu sendiri dihapus. Untuk yatim piatu, entitas utama/induk masih ada, tetapi tidak lagi terkait dengan entitas dependen/anak.

Hubungan banyak ke banyak

Hubungan banyak ke banyak di EF Core diimplementasikan menggunakan entitas gabungan. Setiap sisi hubungan banyak ke banyak terkait dengan entitas gabungan ini dengan hubungan satu-ke-banyak. Entitas gabungan ini dapat didefinisikan dan dipetakan secara eksplisit, atau dapat dibuat secara implisit dan tersembunyi. Dalam kedua kasus, perilaku yang mendasar sama. Kita akan melihat perilaku mendasar ini terlebih dahulu untuk memahami cara kerja pelacakan hubungan banyak-ke-banyak.

Berapa banyak hubungan yang bekerja

Pertimbangkan model EF Core ini yang membuat hubungan banyak ke banyak antara posting dan tag menggunakan jenis entitas gabungan yang ditentukan secara eksplisit:

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int? BlogId { get; set; }
    public Blog Blog { get; set; }

    public IList<PostTag> PostTags { get; } = new List<PostTag>(); // Collection navigation
}

public class Tag
{
    public int Id { get; set; }
    public string Text { get; set; }

    public IList<PostTag> PostTags { get; } = new List<PostTag>(); // Collection navigation
}

public class PostTag
{
    public int PostId { get; set; } // First part of composite PK; FK to Post
    public int TagId { get; set; } // Second part of composite PK; FK to Tag

    public Post Post { get; set; } // Reference navigation
    public Tag Tag { get; set; } // Reference navigation
}

Perhatikan bahwa PostTag jenis entitas gabungan berisi dua properti kunci asing. Dalam model ini, agar postingan terkait dengan tag, harus ada entitas gabungan PostTag di mana PostTag.PostId nilai kunci asing cocok dengan Post.Id nilai kunci utama, dan di mana PostTag.TagId nilai kunci asing cocok dengan Tag.Id nilai kunci utama. Contohnya:

using var context = new BlogsContext();

var post = context.Posts.Single(e => e.Id == 3);
var tag = context.Tags.Single(e => e.Id == 1);

context.Add(new PostTag { PostId = post.Id, TagId = tag.Id });

Console.WriteLine(context.ChangeTracker.DebugView.LongView);

Melihat tampilan debug pelacak perubahan setelah menjalankan kode ini menunjukkan bahwa postingan dan tag terkait dengan entitas gabungan baru PostTag :

Post {Id: 3} Unchanged
  Id: 3 PK
  BlogId: 2 FK
  Content: 'If you are focused on squeezing out the last bits of perform...'
  Title: 'Disassembly improvements for optimized managed debugging'
  Blog: <null>
  PostTags: [{PostId: 3, TagId: 1}]
PostTag {PostId: 3, TagId: 1} Added
  PostId: 3 PK FK
  TagId: 1 PK FK
  Post: {Id: 3}
  Tag: {Id: 1}
Tag {Id: 1} Unchanged
  Id: 1 PK
  Text: '.NET'
  PostTags: [{PostId: 3, TagId: 1}]

Perhatikan bahwa navigasi koleksi aktif Post dan Tag telah diperbaiki, seperti halnya navigasi referensi pada PostTag. Hubungan ini dapat dimanipulasi oleh navigasi alih-alih nilai FK, sama seperti dalam semua contoh sebelumnya. Misalnya, kode di atas dapat dimodifikasi untuk menambahkan hubungan dengan mengatur navigasi referensi pada entitas gabungan:

context.Add(new PostTag { Post = post, Tag = tag });

Ini menghasilkan perubahan yang sama persis pada FK dan navigasi seperti pada contoh sebelumnya.

Lewati navigasi

Memanipulasi tabel gabungan secara manual bisa rumit. Hubungan banyak ke banyak dapat dimanipulasi secara langsung menggunakan navigasi koleksi khusus yang "melewati" entitas gabungan. Misalnya, dua navigasi lewati dapat ditambahkan ke model di atas; satu dari Posting ke Tag, dan yang lainnya dari Tag ke Postingan:

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    public int? BlogId { get; set; }
    public Blog Blog { get; set; }

    public IList<Tag> Tags { get; } = new List<Tag>(); // Skip collection navigation
    public IList<PostTag> PostTags { get; } = new List<PostTag>(); // Collection navigation
}

public class Tag
{
    public int Id { get; set; }
    public string Text { get; set; }

    public IList<Post> Posts { get; } = new List<Post>(); // Skip collection navigation
    public IList<PostTag> PostTags { get; } = new List<PostTag>(); // Collection navigation
}

public class PostTag
{
    public int PostId { get; set; } // First part of composite PK; FK to Post
    public int TagId { get; set; } // Second part of composite PK; FK to Tag

    public Post Post { get; set; } // Reference navigation
    public Tag Tag { get; set; } // Reference navigation
}

Hubungan banyak ke banyak ini memerlukan konfigurasi berikut untuk memastikan navigasi lewati dan navigasi normal semuanya digunakan untuk hubungan banyak ke banyak yang sama:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(p => p.Tags)
        .WithMany(p => p.Posts)
        .UsingEntity<PostTag>(
            j => j.HasOne(t => t.Tag).WithMany(p => p.PostTags),
            j => j.HasOne(t => t.Post).WithMany(p => p.PostTags));
}

Lihat Hubungan untuk informasi selengkapnya tentang pemetaan hubungan banyak ke banyak.

Lewati navigasi terlihat dan berulah seperti navigasi koleksi normal. Namun, cara mereka bekerja dengan nilai kunci asing berbeda. Mari kita kaitkan postingan dengan tag, tetapi kali ini menggunakan navigasi lewati:

using var context = new BlogsContext();

var post = context.Posts.Single(e => e.Id == 3);
var tag = context.Tags.Single(e => e.Id == 1);

post.Tags.Add(tag);

context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);

Perhatikan bahwa kode ini tidak menggunakan entitas gabungan. Sebaliknya hanya menambahkan entitas ke koleksi navigasi dengan cara yang sama seperti yang akan dilakukan jika ini adalah hubungan satu-ke-banyak. Tampilan debug yang dihasilkan pada dasarnya sama seperti sebelumnya:

Post {Id: 3} Unchanged
  Id: 3 PK
  BlogId: 2 FK
  Content: 'If you are focused on squeezing out the last bits of perform...'
  Title: 'Disassembly improvements for optimized managed debugging'
  Blog: <null>
  PostTags: [{PostId: 3, TagId: 1}]
  Tags: [{Id: 1}]
PostTag {PostId: 3, TagId: 1} Added
  PostId: 3 PK FK
  TagId: 1 PK FK
  Post: {Id: 3}
  Tag: {Id: 1}
Tag {Id: 1} Unchanged
  Id: 1 PK
  Text: '.NET'
  PostTags: [{PostId: 3, TagId: 1}]
  Posts: [{Id: 3}]

Perhatikan bahwa instans PostTag entitas gabungan dibuat secara otomatis dengan nilai FK yang diatur ke nilai PK tag dan postingan yang sekarang terkait. Semua referensi normal dan navigasi koleksi telah diperbaiki agar sesuai dengan nilai FK ini. Selain itu, karena model ini berisi navigasi lewati, ini juga telah diperbaiki. Secara khusus, meskipun kami menambahkan tag ke Post.Tags navigasi lewati, Tag.Posts navigasi lewati terbalik di sisi lain hubungan ini juga telah diperbaiki hingga berisi posting terkait.

Perlu dicatat bahwa hubungan banyak-ke-banyak yang mendasar masih dapat dimanipulasi secara langsung bahkan ketika navigasi skip telah berlapis di atas. Misalnya, tag dan Posting dapat dikaitkan seperti yang kami lakukan sebelum memperkenalkan navigasi lewati:

context.Add(new PostTag { Post = post, Tag = tag });

Atau menggunakan nilai FK:

context.Add(new PostTag { PostId = post.Id, TagId = tag.Id });

Ini masih akan mengakibatkan navigasi lewati diperbaiki dengan benar, menghasilkan output tampilan debug yang sama seperti pada contoh sebelumnya.

Lewati navigasi saja

Di bagian sebelumnya kami menambahkan navigasi lewati selain sepenuhnya menentukan dua hubungan satu-ke-banyak yang mendasar. Ini berguna untuk menggambarkan apa yang terjadi pada nilai FK, tetapi seringkali tidak perlu. Sebaliknya, hubungan banyak ke banyak dapat didefinisikan hanya menggunakan navigasi lewati. Ini adalah bagaimana hubungan banyak ke banyak didefinisikan dalam model di bagian paling atas dokumen ini. Dengan menggunakan model ini, kita dapat kembali mengaitkan Postingan dan Tag dengan menambahkan postingan ke Tag.Posts navigasi lewati (atau, secara bergantian, menambahkan tag ke Post.Tags navigasi lewati):

using var context = new BlogsContext();

var post = context.Posts.Single(e => e.Id == 3);
var tag = context.Tags.Single(e => e.Id == 1);

post.Tags.Add(tag);

context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);

Melihat tampilan debug setelah membuat perubahan ini mengungkapkan bahwa EF Core telah membuat instans Dictionary<string, object> untuk mewakili entitas gabungan. Entitas gabungan ini berisi PostsId properti kunci asing dan TagsId yang telah diatur agar sesuai dengan nilai PK posting dan tag yang terkait.

Post {Id: 3} Unchanged
  Id: 3 PK
  BlogId: 2 FK
  Content: 'If you are focused on squeezing out the last bits of perform...'
  Title: 'Disassembly improvements for optimized managed debugging'
  Blog: <null>
  Tags: [{Id: 1}]
Tag {Id: 1} Unchanged
  Id: 1 PK
  Text: '.NET'
  Posts: [{Id: 3}]
PostTag (Dictionary<string, object>) {PostsId: 3, TagsId: 1} Added
  PostsId: 3 PK FK
  TagsId: 1 PK FK

Lihat Hubungan untuk informasi selengkapnya tentang entitas gabungan implisit dan penggunaan Dictionary<string, object> jenis entitas.

Penting

Jenis CLR yang digunakan untuk jenis entitas gabungan menurut konvensi dapat berubah dalam rilis mendatang untuk meningkatkan performa. Jangan bergantung pada jenis Dictionary<string, object> gabungan kecuali ini telah dikonfigurasi secara eksplisit.

Menggabungkan entitas dengan payload

Sejauh ini semua contoh telah menggunakan jenis entitas gabungan (baik eksplisit atau implisit) yang hanya berisi dua properti kunci asing yang diperlukan untuk hubungan banyak-ke-banyak. Tidak satu pun dari nilai FK ini perlu diatur secara eksplisit oleh aplikasi saat memanipulasi hubungan karena nilainya berasal dari properti kunci utama entitas terkait. Ini memungkinkan EF Core untuk membuat instans entitas gabungan tanpa data yang hilang.

Payload dengan nilai yang dihasilkan

EF Core mendukung penambahan properti tambahan ke jenis entitas gabungan. Ini dikenal sebagai memberi entitas gabungan "payload". Misalnya, mari kita tambahkan TaggedOn properti ke PostTag entitas gabungan:

public class PostTag
{
    public int PostId { get; set; } // First part of composite PK; FK to Post
    public int TagId { get; set; } // Second part of composite PK; FK to Tag

    public DateTime TaggedOn { get; set; } // Payload
}

Properti payload ini tidak akan diatur ketika EF Core membuat instans entitas gabungan. Cara paling umum untuk menangani hal ini adalah dengan menggunakan properti payload dengan nilai yang dihasilkan secara otomatis. Misalnya, TaggedOn properti dapat dikonfigurasi untuk menggunakan tanda waktu yang dihasilkan toko saat setiap entitas baru dimasukkan:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>()
        .HasMany(p => p.Tags)
        .WithMany(p => p.Posts)
        .UsingEntity<PostTag>(
            j => j.HasOne<Tag>().WithMany(),
            j => j.HasOne<Post>().WithMany(),
            j => j.Property(e => e.TaggedOn).HasDefaultValueSql("CURRENT_TIMESTAMP"));
}

Postingan sekarang dapat ditandai dengan cara yang sama seperti sebelumnya:

using var context = new BlogsContext();

var post = context.Posts.Single(e => e.Id == 3);
var tag = context.Tags.Single(e => e.Id == 1);

post.Tags.Add(tag);

context.SaveChanges();

Console.WriteLine(context.ChangeTracker.DebugView.LongView);

Melihat tampilan debug pelacak perubahan setelah memanggil SaveChanges menunjukkan bahwa properti payload telah diatur dengan tepat:

Post {Id: 3} Unchanged
  Id: 3 PK
  BlogId: 2 FK
  Content: 'If you are focused on squeezing out the last bits of perform...'
  Title: 'Disassembly improvements for optimized managed debugging'
  Blog: <null>
  Tags: [{Id: 1}]
PostTag {PostId: 3, TagId: 1} Unchanged
  PostId: 3 PK FK
  TagId: 1 PK FK
  TaggedOn: '12/29/2020 8:13:21 PM'
Tag {Id: 1} Unchanged
  Id: 1 PK
  Text: '.NET'
  Posts: [{Id: 3}]

Mengatur nilai payload secara eksplisit

Mengikuti dari contoh sebelumnya, mari kita tambahkan properti payload yang tidak menggunakan nilai yang dihasilkan secara otomatis:

public class PostTag
{
    public int PostId { get; set; } // First part of composite PK; FK to Post
    public int TagId { get; set; } // Second part of composite PK; FK to Tag

    public DateTime TaggedOn { get; set; } // Auto-generated payload property
    public string TaggedBy { get; set; } // Not-generated payload property
}

Postingan sekarang dapat ditandai dengan cara yang sama seperti sebelumnya, dan entitas gabungan masih akan dibuat secara otomatis. Entitas ini kemudian dapat diakses menggunakan salah satu mekanisme yang dijelaskan dalam Mengakses Entitas terlacak. Misalnya, kode di bawah ini menggunakan untuk mengakses instans DbSet<TEntity>.Find entitas gabungan:

using var context = new BlogsContext();

var post = context.Posts.Single(e => e.Id == 3);
var tag = context.Tags.Single(e => e.Id == 1);

post.Tags.Add(tag);

context.ChangeTracker.DetectChanges();

var joinEntity = context.Set<PostTag>().Find(post.Id, tag.Id);

joinEntity.TaggedBy = "ajcvickers";

context.SaveChanges();

Console.WriteLine(context.ChangeTracker.DebugView.LongView);

Setelah entitas gabungan ditemukan, entitas dapat dimanipulasi dengan cara normal--dalam contoh ini, untuk mengatur TaggedBy properti payload sebelum memanggil SaveChanges.

Catatan

Perhatikan bahwa panggilan ke ChangeTracker.DetectChanges() diperlukan di sini untuk memberi EF Core kesempatan untuk mendeteksi perubahan properti navigasi dan membuat instans entitas gabungan sebelum Find digunakan. Lihat Deteksi Perubahan dan Pemberitahuan untuk informasi selengkapnya.

Secara bergantian, entitas gabungan dapat dibuat secara eksplisit untuk mengaitkan postingan dengan tag. Contohnya:

using var context = new BlogsContext();

var post = context.Posts.Single(e => e.Id == 3);
var tag = context.Tags.Single(e => e.Id == 1);

context.Add(
    new PostTag { PostId = post.Id, TagId = tag.Id, TaggedBy = "ajcvickers" });

context.SaveChanges();

Console.WriteLine(context.ChangeTracker.DebugView.LongView);

Terakhir, cara lain untuk mengatur data payload adalah dengan mengesampingkan SaveChanges atau menggunakan DbContext.SavingChanges peristiwa untuk memproses entitas sebelum memperbarui database. Contohnya:

public override int SaveChanges()
{
    foreach (var entityEntry in ChangeTracker.Entries<PostTag>())
    {
        if (entityEntry.State == EntityState.Added)
        {
            entityEntry.Entity.TaggedBy = "ajcvickers";
        }
    }

    return base.SaveChanges();
}