Yabancı Tuşlar ve Gezintileri Değiştirme

Yabancı tuşlara ve gezintilere genel bakış

Entity Framework Core (EF Core) modelindeki ilişkiler yabancı anahtarlar (FK) kullanılarak temsil edilir. FK, ilişkideki bağımlı veya alt varlık üzerindeki bir veya daha fazla özellik içerir. Bağımlı/alt öğedeki yabancı anahtar özelliklerinin değerleri, asıl/üst öğedeki alternatif veya birincil anahtar (PK) özelliklerinin değerleriyle eşleştiğinde, bu bağımlı/alt varlık belirli bir asıl/üst varlıkla ilişkilendirilir.

Yabancı anahtarlar, veritabanında ilişkileri depolamak ve işlemek için iyi bir yoldur, ancak uygulama kodunda birden çok ilgili varlıkla çalışırken çok kolay değildir. Bu nedenle çoğu EF Core modeli de FK gösterimi üzerinde "gezintileri" katmanlar. Gezintiler, yabancı anahtar değerlerini birincil veya alternatif anahtar değerleriyle eşleştirerek bulunan ilişkilendirmeleri yansıtan varlık örnekleri arasında C#/.NET başvuruları oluşturur.

Gezintiler ilişkinin her iki tarafında, yalnızca bir tarafında kullanılabilir veya hiç kullanılamaz, yalnızca FK özelliği bırakılabilir. FK özelliği bir gölge özellik haline getirilerek gizlenebilir. İlişkileri modelleme hakkında daha fazla bilgi için bkz. İlişkiler.

Bahşiş

Bu belgede varlık durumlarının ve EF Core değişiklik izlemenin temellerinin anlaşıldığı varsayılır. Bu konular hakkında daha fazla bilgi için EF Core'daki Değişiklik İzleme bakın.

Bahşiş

GitHub’dan örnek kodu indirerek bu belgedeki tüm kodları çalıştırabilir ve hataları ayıklayabilirsiniz.

Örnek model

Aşağıdaki model, aralarında ilişki bulunan dört varlık türü içerir. Koddaki açıklamalar hangi özelliklerin yabancı anahtarlar, birincil anahtarlar ve gezintiler olduğunu gösterir.

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
}

Bu modeldeki üç ilişki şunlardır:

  • Her blogda birçok gönderi (bire çok) olabilir:
    • Blog asıl/üst öğedir.
    • Post bağımlı/alt öğedir. değerinin ilgili blogun PK değeriyle eşleşmesi Blog.Id gereken FK özelliğini Post.BlogIdiçerir.
    • Post.Blog , bir gönderiden ilişkili bloga başvuru gezintisidir. Post.Blog , için Blog.Poststers gezintidir.
    • Blog.Posts , blogdan ilişkili tüm gönderilere bir koleksiyon gezintisidir. Blog.Posts , için Post.Blogters gezintidir.
  • Her blogda bir varlık (bire bir) bulunabilir:
    • Blog asıl/üst öğedir.
    • BlogAssets bağımlı/alt öğedir. değerinin ilgili blogun PK değeriyle eşleşmesi Blog.Id gereken FK özelliğini BlogAssets.BlogIdiçerir.
    • BlogAssets.Blog , varlıklardan ilişkili bloga bir başvuru gezintisidir. BlogAssets.Blog , için Blog.Assetsters gezintidir.
    • Blog.Assets , blogdan ilişkili varlıklara yönelik bir başvuru gezintisidir. Blog.Assets , için BlogAssets.Blogters gezintidir.
  • Her gönderinin birçok etiketi olabilir ve her etiketin birçok gönderisi olabilir (çoka çok):
    • Çoka çok ilişkileri, iki bire çok ilişkisine göre daha fazla katmandır. Çoka çok ilişkileri bu belgenin ilerleyen bölümlerinde ele alınmıştır.
    • Post.Tags , gönderiden ilişkili tüm etiketlere bir koleksiyon gezintisidir. Post.Tags , için Tag.Poststers gezintidir.
    • Tag.Posts etiketten ilişkili tüm gönderilere bir koleksiyon gezintisidir. Tag.Posts , için Post.Tagsters gezintidir.

İlişkileri modelleme ve yapılandırma hakkında daha fazla bilgi için bkz. İlişkiler.

İlişki düzeltme

EF Core, gezintileri yabancı anahtar değerleriyle aynı hizada tutar ve tam tersi de geçerlidir. Başka bir ifadeyle, yabancı anahtar değeri artık farklı bir asıl/üst varlığa başvuruda bulunacak şekilde değişirse, gezintiler bu değişikliği yansıtacak şekilde güncelleştirilir. Benzer şekilde, bir gezinti değiştirilirse, ilgili varlıkların yabancı anahtar değerleri bu değişikliği yansıtacak şekilde güncelleştirilir. Buna "ilişki düzeltme" adı verilir.

Sorguya göre düzeltme

Düzeltme ilk olarak varlıklar veritabanından sorgulandığında gerçekleşir. Veritabanında yalnızca yabancı anahtar değerleri vardır, bu nedenle EF Core veritabanından bir varlık örneği oluşturduğunda, başvuru gezintilerini ayarlamak ve koleksiyon gezintilerine uygun varlıkları eklemek için yabancı anahtar değerlerini kullanır. Örneğin, bloglar ve ilişkili gönderileri ve varlıkları için bir sorgu düşünün:

using var context = new BlogsContext();

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

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

Her blog için EF Core önce bir Blog örnek oluşturur. Ardından, her gönderi veritabanından Post.Blog yüklendiğinden, başvuru gezintisi ilişkili bloga işaret etmek üzere ayarlanır. Benzer şekilde, gönderi koleksiyon gezintisine Blog.Posts eklenir. ile aynı şey olur BlogAssets, ancak bu durumda her iki gezinti de başvuru olur. Gezinti Blog.Assets , varlık örneğine BlogAsserts.Blog işaret etmek için, gezinti ise blog örneğine işaret etmek üzere ayarlanır.

Bu sorgudan sonra değişiklik izleyicisi hata ayıklama görünümüne baktığımızda her biri bir varlık ve iki gönderinin izlendiği iki blog gösterilir:

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: []

Hata ayıklama görünümü hem anahtar değerlerini hem de gezintileri gösterir. Gezintiler, ilgili varlıkların birincil anahtar değerleri kullanılarak gösterilir. Örneğin, yukarıdaki çıktıda koleksiyon Posts: [{Id: 1}, {Id: 2}] gezintisinin Blog.Posts sırasıyla birincil anahtar 1 ve 2 ile ilişkili iki gönderi içerdiğini gösterir. Benzer şekilde, ilk blogla ilişkili her gönderi için satır, Blog: {Id: 1} gezintinin Post.Blog birincil anahtar 1 ile Blog'a başvurduğunu gösterir.

Yerel olarak izlenen varlıklara düzeltme

İlişki düzeltmesi, bir izleme sorgusundan döndürülen varlıklar ile DbContext tarafından izlenen varlıklar arasında da gerçekleşir. Örneğin bloglar, gönderiler ve varlıklar için üç ayrı sorgu yürütmeyi göz önünde bulundurun:

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

Hata ayıklama görünümlerine yeniden baktığımızda, ilk sorgudan sonra yalnızca iki blog izlenir:

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: []

Başvuru Blog.Assets gezintileri null ve Blog.Posts koleksiyon gezintileri boştur çünkü şu anda bağlam tarafından izlenen hiçbir ilişkili varlık yoktur.

İkinci sorgudan sonra, Blogs.Assets başvuru gezintileri yeni izlenen BlogAsset örnekleri işaret edene kadar düzeltildi. Benzer şekilde, BlogAssets.Blog başvuru gezintileri de zaten izlenen Blog uygun örneğe işaret etmek üzere ayarlanır.

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}

Son olarak, üçüncü sorgudan sonra koleksiyon Blog.Posts gezintileri artık tüm ilgili gönderileri içerir ve Post.Blog başvurular uygun Blog örneğe işaret eder:

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: []

Bu, özgün tek sorguda elde edilen son durumla aynıdır çünkü EF Core, birden çok farklı sorgudan geldiğinde bile varlıklar izlendiğinden gezintileri düzeltir.

Dekont

Düzeltme hiçbir zaman veritabanından daha fazla veri döndürülür. Yalnızca sorgu tarafından zaten döndürülen veya DbContext tarafından zaten izlenen varlıkları bağlar. Varlıkları seri hale getirme sırasında yinelenenleri işleme hakkında bilgi için bkz . EF Core'da Kimlik Çözümlemesi.

Gezintileri kullanarak ilişkileri değiştirme

İki varlık arasındaki ilişkiyi değiştirmenin en kolay yolu, bir gezintiyi düzenlerken EF Core'dan ayrılarak ters gezinti ve FK değerlerini uygun şekilde düzeltmektir. Bunu aşağıdaki yöntemlerle yapabilirsiniz:

  • Koleksiyon gezintisinde varlık ekleme veya kaldırma.
  • Başvuru gezintisini farklı bir varlığa işaret etmek için değiştirme veya null olarak ayarlama.

Koleksiyon gezintilerini ekleme veya kaldırma

Örneğin, Visual Studio blogundaki gönderilerden birini .NET blog'a taşıyalım. Bunun için önce blogların ve gönderilerin yüklenmesi ve ardından gönderinin bir blogdaki gezinti koleksiyonundan diğer blogdaki gezinti koleksiyonuna taşınması gerekir:

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();

Bahşiş

Hata ayıklama görünümüne ChangeTracker.DetectChanges() erişmek değişikliklerin otomatik olarak algılanmasına neden olmadığından çağrısı burada gereklidir.

Bu, yukarıdaki kodu çalıştırdıktan sonra yazdırılan hata ayıklama görünümüdür:

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: []

Blog.Posts.NET Blogu'nda gezintide artık üç gönderi (Posts: [{Id: 1}, {Id: 2}, {Id: 3}]) vardır. Benzer şekilde, Blog.Posts Visual Studio blogundaki gezintide de yalnızca bir gönderi (Posts: [{Id: 4}]) vardır. Kod bu koleksiyonları açıkça değiştirdiğinden bu beklenen bir durumdur.

Daha da ilginç olan, kod gezintiyi açıkça değiştirmese Post.Blog de Visual Studio blogunu (Blog: {Id: 1} ) işaret edecek şekilde düzeltilmiştir. Ayrıca, Post.BlogId yabancı anahtar değeri .NET blogunun birincil anahtar değeriyle eşleşecek şekilde güncelleştirildi. Içindeki FK değerinde yapılan bu değişiklik, SaveChanges çağrıldığında veritabanında kalıcı hale getirmek için:

-- 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();

Başvuru gezintilerini değiştirme

Önceki örnekte, her blogdaki gönderilerin koleksiyon gezintisi değiştirilerek bir gönderi bir blogdan diğerine taşındı. Aynı şey, başvuru gezintisini yeni bloga Post.Blog işaret eden şekilde değiştirerek de elde edilebilir. Örnek:

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

Bu değişiklik sonrasındaki hata ayıklama görünümü, önceki örnektekiyle tamamen aynıdır . Bunun nedeni EF Core'un başvuru gezinti değişikliğini algılayıp koleksiyon gezintilerini ve FK değerini eşleşecek şekilde düzeltmesidir.

Yabancı anahtar değerlerini kullanarak ilişkileri değiştirme

Önceki bölümde ilişkiler, yabancı anahtar değerlerinin otomatik olarak güncelleştirilecek şekilde bırakılmasına neden olan gezintiler tarafından işleniyordu. EF Core'da ilişkileri işlemenin önerilen yolu budur. Ancak, FK değerlerini doğrudan işlemek de mümkündür. Örneğin, yabancı anahtar değerini değiştirerek bir gönderiyi Post.BlogId bir blogdan diğerine taşıyabiliriz:

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

Bunun, önceki örnekte gösterildiği gibi başvuru gezintisini değiştirmeye çok benzer olduğuna dikkat edin.

Bu değişiklik sonrasındaki hata ayıklama görünümü, önceki iki örnekte olduğu gibi yine tam olarak aynıdır . Bunun nedeni EF Core'un FK değer değişikliğini algılayıp hem başvuru hem de koleksiyon gezintilerini eşleşecek şekilde düzeltmesidir.

Bahşiş

bir ilişki her değiştiğinde tüm gezintileri ve FK değerlerini işlemek için kod yazmayın. Bu tür kod daha karmaşıktır ve her durumda yabancı anahtarlarda ve gezintilerde tutarlı değişiklikler olmasını sağlamalıdır. Mümkünse tek bir gezintiyi veya her iki gezintiyi de işlemeniz yeterlidir. Gerekirse FK değerlerini işlemeye devam edin. Hem gezintileri hem de FK değerlerini düzenlemekten kaçının.

Eklenen veya silinen varlıklar için düzeltme

Koleksiyon gezintisine ekleme

EF Core, koleksiyon gezintisine yeni bir bağımlı/alt varlık eklendiğini algıladığında aşağıdaki eylemleri gerçekleştirir:

  • Varlık izlenmiyorsa izlenir. (Varlık genellikle durumunda olur Added . Ancak varlık türü oluşturulan anahtarları kullanacak şekilde yapılandırılmışsa ve birincil anahtar değeri ayarlanmışsa varlık durumunda izlenir Unchanged .)
  • Varlık farklı bir sorumlu/üst öğeyle ilişkiliyse, bu ilişki kesilir.
  • Varlık, koleksiyon gezintisine sahip olan asıl/üst öğeyle ilişkilendirildi.
  • Gezintiler ve yabancı anahtar değerleri, ilgili tüm varlıklar için düzeltilir.

Buna dayanarak, bir gönderiyi bir blogdan diğerine taşımak için yenisine eklemeden önce eski koleksiyon gezintisinden kaldırmamıza gerek olmadığını görebiliriz. Bu nedenle yukarıdaki örnekteki kod şu şekilde değiştirilebilir:

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

Hedef:

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

EF Core, gönderinin yeni bir bloga eklendiğini görür ve ilk blogdaki koleksiyondan otomatik olarak kaldırır.

Koleksiyon gezintisinden kaldırma

Bağımlı/alt varlığın asıl/üst öğesinin koleksiyon gezintisinden kaldırılması, ilgili sorumlu/üst öğeyle ilişkinin kesilmesine neden olur. Bundan sonra ne olacağı ilişkinin isteğe bağlı mı yoksa gerekli mi olduğuna bağlıdır.

İsteğe bağlı ilişkiler

İsteğe bağlı ilişkiler için varsayılan olarak yabancı anahtar değeri null olarak ayarlanır. Bu, bağımlı/alt öğesinin artık hiçbir sorumlu/üst öğeyle ilişkilendirilmemiş olduğu anlamına gelir. Örneğin, bir blog ve gönderi yükleyelim ve ardından gönderilerden birini koleksiyon gezintisinden Blog.Posts kaldıralım:

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

Bu değişiklik sonrasında değişiklik izleme hata ayıklama görünümüne bakmak şunları gösterir:

  • Post.BlogId FK null (BlogId: <null> FK Modified Originally 1) olarak ayarlandı
  • Başvuru Post.Blog gezintisi null (Blog: <null>) olarak ayarlandı
  • Gönderi koleksiyon gezintisinden Blog.Posts kaldırıldı (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: []

Gönderinin olarak Deletedişaretlenmediğini fark edin. SaveChanges çağrıldığında veritabanındaki FK değerinin null olarak ayarlanması için olarak Modified işaretlenir.

Gerekli ilişkiler

Gerekli ilişkiler için FK değerini null olarak ayarlamaya izin verilmez (ve genellikle mümkün değildir). Bu nedenle, gerekli bir ilişkinin kesilmesi, bağımlı/alt varlığın yeni bir sorumluya/üst öğeye yeniden üst öğe olarak atanması veya başvuru kısıtlama ihlalini önlemek için SaveChanges çağrıldığında veritabanından kaldırılması gerektiği anlamına gelir. Bu, "yalnız bırakılmışları silme" olarak bilinir ve EF Core'da gerekli ilişkiler için varsayılan davranıştır.

Örneğin, blog ile gönderiler arasındaki ilişkiyi gerekli olacak şekilde değiştirelim ve ardından önceki örnektekiyle aynı kodu çalıştıralım:

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

Bu değişiklik sonrasında hata ayıklama görünümüne bakmak şunları gösterir:

  • Gönderi, SaveChanges çağrıldığında veritabanından silinecek şekilde Deleted işaretlendi.
  • Başvuru Post.Blog gezintisi null (Blog: <null> olarak ayarlandı).
  • Gönderi koleksiyon gezintisinden Blog.Posts (Posts: [{Id: 1}] ) kaldırıldı.
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: []

Post.BlogId Gerekli bir ilişki için null olarak ayarlanamadığından değerinin değişmeden kaldığına dikkat edin.

SaveChanges çağrısı, yalnız bırakılmış gönderinin silinmesiyle sonuçlanır:

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

Yalnız bırakılmışların zamanlamasını ve yeniden üst öğeyi silme

Varsayılan olarak, yalnız bırakılmışları olarak Deleted işaretleme, ilişki değişikliği algılanır algılanmaz gerçekleşir. Ancak, SaveChanges gerçekten çağrılana kadar bu işlem geciktirilebilir. Bu, bir sorumludan/üst öğeden kaldırılmış ancak SaveChanges çağrılmadan önce yeni bir sorumlu/üst öğeyle yeniden üst öğeye sahip olacak varlıkların yalnız bırakmasını önlemek için yararlı olabilir. ChangeTracker.DeleteOrphansTiming bu zamanlamayı ayarlamak için kullanılır. Örnek:

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();

gönderi ilk koleksiyondan kaldırıldıktan sonra nesne önceki örnekte olduğu gibi Deleted işaretlenmez. Bunun yerine EF Core, bu gerekli bir ilişki olsa bile ilişkinin kesildiğini izliyor. (FK değeri, ef core tarafından null olarak kabul edilir, ancak türü null değer atanamaz. Bu, "kavramsal null" olarak bilinir.)

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: []

Şu anda SaveChanges çağrısı, yalnız bırakılmış gönderinin silinmesine neden olur. Ancak yukarıdaki örnekte olduğu gibi, SaveChanges çağrılmadan önce gönderi yeni bir blogla ilişkilendirilirse, bu yeni bloga uygun şekilde düzeltilir ve artık yalnız bırakılmış olarak kabul edilmez:

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: []

Bu noktada çağrılan SaveChanges, veritabanındaki gönderiyi silmek yerine güncelleştirir.

Yalnız bırakılmışların otomatik olarak silinmesini kapatmak da mümkündür. Bir yalnız bırakılmış öğe izlenirken SaveChanges çağrılırsa bu bir özel durumla sonuçlanır. Örneğin, bu kod:

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

Şu özel durumu oluşturur:

System.InvalidOperationException:'{BlogId: 1}' anahtar değeriyle 'Blog' ve 'Post' varlıkları arasındaki ilişki koptu, ancak ilişki gerekli olarak işaretlendi veya yabancı anahtar null atanamaz olduğundan örtük olarak gerekli. Gerekli bir ilişki kesildiğinde bağımlı/alt varlığın silinmesi gerekiyorsa, ilişkiyi art arda silmeleri kullanacak şekilde yapılandırın.

Artıkların silinmesi ve art arda silme işlemleri istendiği zaman çağrılarak ChangeTracker.CascadeChanges()zorlanabilir. Bunu artık silme zamanlamasını Never ayarlamakla birleştirdiğinizde, EF Core açıkça yönerge almadığı sürece yalnız bırakılmış öğeler hiçbir zaman silinmez.

Başvuru gezintisini değiştirme

Bire çok ilişkinin başvuru gezintisini değiştirmek, ilişkinin diğer ucundaki koleksiyon gezintisini değiştirmekle aynı etkiye sahiptir. Bağımlı/alt öğesinin başvuru gezintisini null olarak ayarlamak, varlığı asıl/üst öğe koleksiyonu gezintisinden kaldırmaya eşdeğerdir. Tüm düzeltme ve veritabanı değişiklikleri, ilişki gerekiyorsa varlığı yalnız bırakılmış hale getirmek de dahil olmak üzere önceki bölümde açıklandığı gibi gerçekleşir.

İsteğe bağlı bire bir ilişkiler

Bire bir ilişkilerde, başvuru gezintisini değiştirmek önceki tüm ilişkilerin kesilmesine neden olur. İsteğe bağlı ilişkiler için bu, daha önce ilişkili bağımlı/alt öğedeki FK değerinin null olarak ayarlandığı anlamına gelir. Örnek:

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();

SaveChanges çağrılmadan önceki hata ayıklama görünümü, yeni varlıkların mevcut varlıkların yerini aldığı ve artık null BlogAssets.BlogId bir FK değeriyle işaretlenmiş Modified olduğunu gösterir:

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>

Bu, SaveChanges çağrıldığında bir güncelleştirme ve ekleme ile sonuçlanır:

-- 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();

Gerekli bire bir ilişkiler

Önceki örnektekiyle aynı kodu çalıştırmak, ancak bu kez gerekli bire bir ilişkiyle, yeni BlogAssets yerine geçtiğinde yalnız bırakılmış duruma geldiğinden, daha önce ilişkilendirilmiş BlogAssets olanın olarak Deletedişaretlendiğini gösterir:

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>

Ardından SaveChanges çağrıldığında silme ve ekleme işlemiyle sonuçlanır:

-- 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();

Artıkları silinmiş olarak işaretleme zamanlaması, koleksiyon gezintilerinde gösterildiği gibi değiştirilebilir ve aynı etkilere sahiptir.

Varlık silme

İsteğe bağlı ilişkiler

Bir varlık olarak Deletedişaretlendiğinde, örneğin çağrısı DbContext.Removeyapıldığında, silinen varlığa yapılan başvurular diğer varlıkların gezintilerinden kaldırılır. İsteğe bağlı ilişkiler için, bağımlı varlıklardaki FK değerleri null olarak ayarlanır.

Örneğin, Visual Studio blogunu olarak Deletedişaretleyelim:

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();

SaveChanges çağrılmadan önce değişiklik izleyicisi hata ayıklama görünümüne bakmak şunları gösterir:

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: []

Şu noktalara dikkat edin:

  • Blog Deleted olarak işaretlendi.
  • Silinen blogla ilgili varlıklar null bir FK değerine (BlogId: <null> FK Modified Originally 2) ve null başvuru gezintisine (Blog: <null>) sahip
  • Silinen blogla ilgili her gönderinin bir null FK değeri (BlogId: <null> FK Modified Originally 2) ve null başvuru gezintisi (Blog: <null>) vardır

Gerekli ilişkiler

Gerekli ilişkiler için düzeltme davranışı isteğe bağlı ilişkilerle aynıdır, ancak bağımlı/alt varlıklar bir sorumlu/üst öğe olmadan var olamazlar olarak Deleted işaretlenir ve başvuru kısıtlaması özel durumunu önlemek için SaveChanges çağrıldığında veritabanından kaldırılmalıdır. Bu, "art arda silme" olarak bilinir ve EF Core'da gerekli ilişkiler için varsayılan davranıştır. Örneğin, önceki örnekte olduğu gibi ancak gerekli bir ilişkiyle aynı kodun çalıştırılması SaveChanges çağrılmadan önce aşağıdaki hata ayıklama görünümünde sonuçlanır:

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: []

Beklendiği gibi, bağımlılar/alt öğeler olarak işaretlenir Deleted. Ancak, silinen varlıklardaki gezintilerin değişmediğine dikkat edin. Bu garip görünebilir, ancak tüm gezintileri temizleyerek varlıkların silinmiş grafiğini tamamen parçalamaktan kaçınıyor. Başka bir ifadeyle, blog, varlık ve gönderiler silindikten sonra bile varlıkların grafiğini oluşturur. Bu, ef6'da grafiğin parçalandığı durumdan çok daha kolay bir varlık grafiğinin silinmesini kolaylaştırır.

Silme zamanlamasını art arda silme ve yeniden üst öğe oluşturma

Varsayılan olarak, üst/sorumlu olarak Deletedişaretlenir işaretlenmez art arda silme gerçekleşir. Bu, daha önce açıklandığı gibi yalnız bırakılmışları silme işlemiyle aynıdır. Artıkları silerken olduğu gibi, bu işlem de SaveChanges çağrılana kadar gecikebilir, hatta uygun şekilde ayarlanarak ChangeTracker.CascadeDeleteTiming tamamen devre dışı bırakılabilir. Bu, bir asıl/üst öğe silindikten sonra alt öğeleri/bağımlıları yeniden üst öğelendirme de dahil olmak üzere yalnız bırakılmışları silme işlemiyle aynı şekilde yararlıdır.

Artıkları art arda silmenin yanı sıra artıkları silme işlemi, herhangi bir zamanda çağrılarak ChangeTracker.CascadeChanges()zorlanabilir. Bunu art arda silme zamanlamasını Never olarak ayarlamakla birleştirmek, EF Core'a açıkça bunu yapması belirtilmediği sürece art arda silmelerin hiçbir zaman gerçekleşmemesini sağlar.

Bahşiş

Artıkları art arda silme ve silme işlemleri yakından ilişkilidir. Her ikisi de gerekli sorumlu/üst öğeyle ilişki kesildiğinde bağımlı/alt varlıkların silinmesiyle sonuçlanır. Art arda silme için bu kesme işlemi, asıl/üst öğe silindiği için gerçekleşir. Yalnız bırakılmışlar için, asıl/üst varlık hala var, ancak artık bağımlı/alt varlıklarla ilişkili değil.

Çoka çok ilişkiler

EF Core'da çoka çok ilişkiler birleştirme varlığı kullanılarak uygulanır. Çoka çok ilişkisinin her tarafı, bire çok ilişkisi olan bu birleşim varlığıyla ilişkilidir. Bu birleştirme varlığı açıkça tanımlanabilir ve eşlenebilir veya örtük olarak oluşturulup gizlenebilir. Her iki durumda da temel alınan davranış aynıdır. Çoka çok ilişkileri izlemenin nasıl çalıştığını anlamak için öncelikle bu temel davranışı inceleyeceğiz.

Kaça çok ilişkiler çalışır?

Açıkça tanımlanmış birleştirme varlık türünü kullanarak gönderiler ve etiketler arasında çoka çok ilişki oluşturan bu EF Core modelini göz önünde bulundurun:

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
}

PostTag Birleştirme varlık türünün iki yabancı anahtar özelliği içerdiğine dikkat edin. Bu modelde, bir gönderinin etiketle ilişkili olması için, yabancı anahtar değerinin PostTag.PostId birincil anahtar değeriyle eşleştiği ve yabancı anahtar değerinin Post.Id birincil anahtar değeriyle Tag.Id eşleştiği PostTag.TagId postTag birleştirme varlığı olmalıdır. Örnek:

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

Bu kodu çalıştırdıktan sonra değişiklik izleyicisi hata ayıklama görünümüne baktığımızda gönderi ve etiketin yeni PostTag birleştirme varlığıyla ilişkili olduğu gösterilir:

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

üzerinde başvuru gezintileri Post olduğu gibi üzerinde ve Tag koleksiyon gezintilerinin PostTagdüzeltildiğine dikkat edin. Bu ilişkiler, önceki tüm örneklerde olduğu gibi FK değerleri yerine gezintiler tarafından işlenebilir. Örneğin, birleştirme varlığında başvuru gezintileri ayarlanarak yukarıdaki kod ilişkiyi eklemek için değiştirilebilir:

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

Bu, FK'lerde ve gezintilerde önceki örnektekiyle tam olarak aynı değişikliğe neden olur.

Gezintileri atlama

Birleştirme tablosunu el ile düzenlemek zahmetli olabilir. Çoka çok ilişkiler, birleştirme varlığını "atlayan" özel koleksiyon gezintileri kullanılarak doğrudan işlenebilir. Örneğin, yukarıdaki modele iki atlama gezintisi eklenebilir; Biri Gönderiden Etiketlere, diğeri etiketten gönderilere:

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
}

Bu çoka çok ilişkisi, atlama gezintilerinin ve normal gezintilerin aynı çoka çok ilişkisi için kullanıldığından emin olmak için aşağıdaki yapılandırmayı gerektirir:

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

Çoka çok ilişkileri eşleme hakkında daha fazla bilgi için bkz . İlişkiler .

Atlama gezintileri normal koleksiyon gezintileri gibi görünür ve davranır. Ancak, yabancı anahtar değerleriyle çalışma yöntemleri farklıdır. Bir gönderiyi bir etiketle ilişkilendirelim, ancak bu kez bir atlama gezintisi kullanarak:

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

Bu kodun birleştirme varlığını kullanmadığını fark edin. Bunun yerine, bir gezinti koleksiyonuna bire çok ilişkide olduğu gibi bir varlık ekler. Sonuçta elde edilen hata ayıklama görünümü temelde öncekiyle aynıdır:

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

Birleştirme varlığının bir örneğinin PostTag , FK değerleri artık ilişkili olan etiketin ve gönderinin PK değerlerine ayarlanmış şekilde otomatik olarak oluşturulduğuna dikkat edin. Tüm normal başvuru ve koleksiyon gezintileri bu FK değerleriyle eşleşecek şekilde düzeltildi. Ayrıca, bu model atlama gezintileri içerdiğinden bunlar da düzeltilmiştir. Özellikle, atla gezintisine Post.Tags etiketi eklemiş olsak da, Tag.Posts bu ilişkinin diğer tarafındaki ters atlama gezintisi de ilişkili gönderiyi içerecek şekilde düzeltilmiştir.

Temel alınan çoka çok ilişkilerin, atlama gezintileri en üste katmanlandığında bile doğrudan yönlendirilebileceğini belirtmek gerekir. Örneğin, atla gezintilerini kullanıma sunmadan önce yaptığımız gibi etiket ve Gönderi ilişkilendirilebilir:

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

Veya FK değerlerini kullanarak:

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

Bu, atlama gezintilerinin doğru şekilde düzeltilmesine ve önceki örnektekiyle aynı hata ayıklama görünümü çıkışına neden olmaya devam eder.

Yalnızca gezintileri atla

Önceki bölümde, temel alınan iki bire çok ilişkiyi tam olarak tanımlamaya ek olarak atlama gezintileri ekledik. Bu, FK değerlerine ne olduğunu göstermek için yararlıdır, ancak genellikle gereksizdir. Bunun yerine, çoka çok ilişkisi yalnızca atlama gezintileri kullanılarak tanımlanabilir. Bu belgenin en üstündeki modelde çoka çok ilişkisi bu şekilde tanımlanır. Bu modeli kullanarak, atla gezintisine gönderi ekleyerek (veya alternatif olarak atlama gezintisine Tag.Posts etiket ekleyerek) gönderiyi Post.Tags ve Etiketi yeniden ilişkilendirebiliriz:

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

Bu değişikliği yaptıktan sonra hata ayıklama görünümüne baktığımızda EF Core'un birleştirme varlığını temsil eden bir örneği Dictionary<string, object> oluşturduğunu gösterir. Bu birleştirme varlığı, ilişkili gönderi ve TagsId etiketin PK değerleriyle eşleşecek şekilde ayarlanmış hem hem de PostsId yabancı anahtar özelliklerini içerir.

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

Örtük birleştirme varlıkları ve varlık türlerinin Dictionary<string, object> kullanımı hakkında daha fazla bilgi için bkz. İlişkiler.

Önemli

Birleştirme varlık türleri için kurala göre kullanılan CLR türü, performansı geliştirmek için gelecek sürümlerde değişebilir. Bu açıkça yapılandırılmadığı sürece birleştirme türüne Dictionary<string, object> bağımlı olmayın.

Yükleri olan varlıkları birleştirme

Şimdiye kadar tüm örnekler, çoka çok ilişkisi için gereken iki yabancı anahtar özelliğini içeren bir birleştirme varlık türü (açık veya örtük) kullandı. Bu FK değerlerinin, ilişkileri işlerken uygulama tarafından açıkça ayarlanması gerekmez çünkü değerleri ilgili varlıkların birincil anahtar özelliklerinden gelir. Bu, EF Core'un eksik veri olmadan birleştirme varlığının örneklerini oluşturmasına olanak tanır.

Oluşturulan değerlere sahip yükler

EF Core, birleştirme varlık türüne ek özellikler eklemeyi destekler. Bu, birleştirme varlığına bir "yük" vermek olarak bilinir. Örneğin, birleştirme varlığına özellik PostTag ekleyelimTaggedOn:

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
}

EF Core bir birleştirme varlık örneği oluşturduğunda bu payload özelliği ayarlanmaz. Bununla başa çıkmanın en yaygın yolu, otomatik olarak oluşturulan değerlerle yük özelliklerini kullanmaktır. Örneğin özelliği, TaggedOn her yeni varlık eklendiğinde depo tarafından oluşturulan bir zaman damgası kullanacak şekilde yapılandırılabilir:

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

Bir gönderi artık öncekiyle aynı şekilde etiketlenebilir:

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

SaveChanges çağrıldıktan sonra değişiklik izleyicisi hata ayıklama görünümüne baktığımızda payload özelliğinin uygun şekilde ayarlandığı gösterilir:

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

Yük değerlerini açıkça ayarlama

Önceki örnekten itibaren, otomatik olarak oluşturulan değeri kullanmayan bir payload özelliği ekleyelim:

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
}

Bir gönderi artık önceki gibi etiketlenebilir ve birleştirme varlığı otomatik olarak oluşturulmaya devam eder. Daha sonra bu varlığa İzlenen Varlıklara Erişme bölümünde açıklanan mekanizmalardan biri kullanılarak erişilebilir. Örneğin, aşağıdaki kod birleştirme varlık örneğine erişmek için kullanır DbSet<TEntity>.Find :

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

Birleştirme varlığı bulunduktan sonra, SaveChanges çağrılmadan önce payload özelliğini ayarlamak TaggedBy için normal şekilde işlenebilir.

Dekont

EF Core'a ChangeTracker.DetectChanges() gezinti özelliği değişikliğini algılama ve kullanılmadan önce Find birleştirme varlık örneğini oluşturma şansı vermek için burada çağrısı yapılması gerektiğini unutmayın. Daha fazla bilgi için bkz . Değişiklik Algılama ve Bildirimler .

Alternatif olarak, bir gönderiyi bir etiketle ilişkilendirmek için birleştirme varlığı açıkça oluşturulabilir. Örnek:

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

Son olarak, yük verilerini ayarlamanın bir diğer yolu da veritabanını güncelleştirmeden önce varlıkları işlemek için olayı geçersiz kılması SaveChanges veya kullanılmasıdır DbContext.SavingChanges . Örnek:

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

    return base.SaveChanges();
}