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 propertiPost.BlogId
FK , nilai yang harus cocok denganBlog.Id
nilai PK blog terkait.Post.Blog
adalah navigasi referensi dari postingan ke blog terkait.Post.Blog
adalah navigasi terbalik untukBlog.Posts
.Blog.Posts
adalah navigasi koleksi dari blog ke semua posting terkait.Blog.Posts
adalah navigasi terbalik untukPost.Blog
.
- Setiap blog dapat memiliki satu aset (satu-ke-satu):
Blog
adalah prinsipal/induk.BlogAssets
adalah dependen/anak. Ini berisi propertiBlogAssets.BlogId
FK , nilai yang harus cocok denganBlog.Id
nilai PK blog terkait.BlogAssets.Blog
adalah navigasi referensi dari aset ke blog terkait.BlogAssets.Blog
adalah navigasi terbalik untukBlog.Assets
.Blog.Assets
adalah navigasi referensi dari blog ke aset terkait.Blog.Assets
adalah navigasi terbalik untukBlogAssets.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 untukTag.Posts
.Tag.Posts
adalah navigasi koleksi dari tag ke semua postingan terkait.Tag.Posts
adalah navigasi terbalik untukPost.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 = await context.Blogs
.Include(e => e.Posts)
.Include(e => e.Assets)
.ToListAsync();
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 = await context.Blogs.ToListAsync();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
var assets = await context.Assets.ToListAsync();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
var posts = await context.Posts.ToListAsync();
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 = await context.Blogs.Include(e => e.Posts).SingleAsync(e => e.Name == ".NET Blog");
var vsBlog = await context.Blogs.Include(e => e.Posts).SingleAsync(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);
await context.SaveChangesAsync();
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 dalamUnchanged
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);
await context.SaveChangesAsync();
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 = await context.Blogs.Include(e => e.Posts).SingleAsync(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);
await context.SaveChangesAsync(); // 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 = await context.Blogs.Include(e => e.Assets).SingleAsync(e => e.Name == ".NET Blog");
dotNetBlog.Assets = new BlogAssets();
context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
await context.SaveChangesAsync();
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 = await context.Blogs
.Include(e => e.Posts)
.Include(e => e.Assets)
.SingleAsync(e => e.Name == "Visual Studio Blog");
context.Remove(vsBlog);
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
await context.SaveChangesAsync();
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 = await context.Posts.SingleAsync(e => e.Id == 3);
var tag = await context.Tags.SingleAsync(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 = await context.Posts.SingleAsync(e => e.Id == 3);
var tag = await context.Tags.SingleAsync(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 = await context.Posts.SingleAsync(e => e.Id == 3);
var tag = await context.Tags.SingleAsync(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 = await context.Posts.SingleAsync(e => e.Id == 3);
var tag = await context.Tags.SingleAsync(e => e.Id == 1);
post.Tags.Add(tag);
await context.SaveChangesAsync();
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 = await context.Posts.SingleAsync(e => e.Id == 3);
var tag = await context.Tags.SingleAsync(e => e.Id == 1);
post.Tags.Add(tag);
context.ChangeTracker.DetectChanges();
var joinEntity = await context.Set<PostTag>().FindAsync(post.Id, tag.Id);
joinEntity.TaggedBy = "ajcvickers";
await context.SaveChangesAsync();
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.SingleAsync(e => e.Id == 3);
var tag = context.Tags.SingleAsync(e => e.Id == 1);
context.Add(
new PostTag { PostId = post.Id, TagId = tag.Id, TaggedBy = "ajcvickers" });
await context.SaveChangesAsync();
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 async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
foreach (var entityEntry in ChangeTracker.Entries<PostTag>())
{
if (entityEntry.State == EntityState.Added)
{
entityEntry.Entity.TaggedBy = "ajcvickers";
}
}
return await base.SaveChangesAsync(cancellationToken);
}