Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Přehled cizích klíčů a navigace
Relace v modelu Entity Framework Core (EF Core) jsou reprezentované pomocí cizích klíčů (FK). FK se skládá z jedné nebo více vlastností závislé, případně podřízené entity v rámci relace. Tato závislá/podřízená entita je přidružená k dané hlavní/nadřazené entitě, pokud hodnoty vlastností cizího klíče na závislé/podřízené entitě odpovídají hodnotám alternativního nebo primárního klíče (PK) na hlavní/nadřazené entitě.
Cizí klíče představují dobrý způsob, jak ukládat a manipulovat s relacemi v databázi, ale nejsou při práci s více souvisejícími entitami v kódu aplikace velmi přívětivé. Většina modelů EF Core proto také vrství "navigace" nad reprezentací FK. Navigace tvoří odkazy C#/.NET mezi instancemi entit, které odrážejí přidružení nalezená odpovídajícími hodnotami cizího klíče k primárním nebo alternativním hodnotám klíče.
Navigace lze použít na obou stranách relace, pouze na jedné straně nebo ne vůbec, a ponechat pouze vlastnost FK. Vlastnost FK může být skrytá tím, že se jedná o stínovou vlastnost. Další informace o modelování relací najdete v tématu Relace .
Návod
Tento dokument předpokládá, že stavy entit a základy sledování změn EF Core jsou srozumitelné. Další informace o těchto tématech najdete v tématu Sledování změn v EF Core .
Návod
Veškerý kód v tomto dokumentu můžete spustit a ladit stažením ukázkového kódu z GitHubu.
Příklad modelu
Následující model obsahuje čtyři typy entit s relacemi mezi nimi. Komentáře v kódu označují, které vlastnosti jsou cizí klíče, primární klíče a navigační vlastnosti.
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
}
Tři relace v tomto modelu jsou:
- Každý blog může mít mnoho příspěvků (1:N):
-
Blogje hlavní subjekt nebo rodič. -
Postje závislý/podřízený. Obsahuje vlastnost FKPost.BlogId, jejíž hodnota musí odpovídat hodnotě PKBlog.Idsouvisejícího blogu. -
Post.Blogje referenční navigace z příspěvku na přidružený blog.Post.Blogje inverzní navigace proBlog.Posts. -
Blog.Postsje navigace kolekce z blogu na všechny přidružené příspěvky.Blog.Postsje inverzní navigace proPost.Blog.
-
- Každý blog může mít jeden prostředek (1:1):
-
Blogje hlavní subjekt nebo rodič. -
BlogAssetsje závislý/podřízený. Obsahuje vlastnost FKBlogAssets.BlogId, jejíž hodnota musí odpovídat hodnotě PKBlog.Idsouvisejícího blogu. -
BlogAssets.Blogje navigace sloužící jako reference ze zdrojů na přidružený blog.BlogAssets.Blogje inverzní navigace proBlog.Assets. -
Blog.Assetsje referenční navigace z blogu na přidružené aktiva.Blog.Assetsje inverzní navigace proBlogAssets.Blog.
-
- Každý příspěvek může mít mnoho značek a každá značka může mít mnoho příspěvků (M:N):
- Relace M:N jsou další vrstvou nad dvěma relacemi 1:N. Relace mnoho ku mnoha jsou popsány dále v tomto dokumentu.
-
Post.Tagsje navigace pro kolekci z příspěvku ke všem přidruženým značkám.Post.Tagsje inverzní navigace proTag.Posts. -
Tag.Postspředstavuje navigaci v rámci kolekce od značky ke všem souvisejícím příspěvkům.Tag.Postsje inverzní navigace proPost.Tags.
Další informace o modelování a konfiguraci relací najdete v tématu Relace .
Oprava vztahu
EF Core udržuje své navigační vlastnosti v souladu s hodnotami cizích klíčů a naopak. To znamená, že jestliže se hodnota cizího klíče změní tak, aby teď odkazovala na jinou hlavní nebo nadřazenou entitu, navigace se aktualizují tak, aby odrážely tuto změnu. Podobně platí, že pokud se změní navigace, aktualizují se hodnoty cizího klíče zúčastněných entit tak, aby odrážely tuto změnu. Říká se tomu "oprava vztahů".
Oprava podle dotazu
K opravě dochází nejprve, když jsou entity dotazovány z databáze. Databáze má pouze hodnoty cizího klíče, takže když EF Core vytvoří instanci entity z databáze, použije hodnoty cizího klíče k nastavení referenčních navigace a přidání entit do navigace kolekce podle potřeby. Představte si například dotaz na blogy a související příspěvky a prostředky:
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);
Pro každý blog EF Core nejprve vytvoří Blog instanci. Když se pak každý příspěvek načte z databáze, Post.Blog je jeho referenční navigace nastavená tak, aby odkazovala na přidružený blog. Podobně se příspěvek přidá do navigace v kolekci Blog.Posts . Totéž se děje s BlogAssets, s výjimkou v tomto případě obě navigace jsou odkazy. Navigace Blog.Assets je nastavená tak, aby ukazovala na instanci assetů, a navigace BlogAsserts.Blog je nastavená tak, aby ukazovala na instanci blogu.
Když se podíváte na zobrazení ladění sledování změn po tomto dotazu, zobrazí se dva blogy, z nichž každý má jeden mediální soubor a sledují se dva příspěvky:
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: []
Zobrazení ladění zobrazuje klíčové hodnoty i navigace. Navigace se zobrazují pomocí hodnot primárního klíče souvisejících entit. Například výstup výše znamená, že Posts: [{Id: 1}, {Id: 2}] navigace v kolekci Blog.Posts obsahuje dva související příspěvky s primárními klíči 1 a 2, v uvedeném pořadí. Podobně pro každý příspěvek spojený s prvním Blogem řádek Blog: {Id: 1} označuje, že navigace Post.Blog odkazuje na Blog s primárním klíčem 1.
Oprava místně sledovaných entit
Oprava relací se také děje mezi entitami vrácenými z sledovacího dotazu a entitami, které už dbContext sleduje. Zvažte například spuštění tří samostatných dotazů pro blogy, příspěvky a zdroje:
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);
Když se znovu podíváte na zobrazení ladění, po prvním dotazu se sledují jenom dva blogy:
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: []
Referenční Blog.Assets navigace mají hodnotu null a Blog.Posts navigace v kolekci jsou prázdné, protože kontext aktuálně nesleduje žádné přidružené entity.
Po druhém dotazu byly referenční navigace opraveny tak, Blogs.Assets aby odkazovaly na nově sledované BlogAsset instance. Podobně jsou referenční navigace nastaveny tak, aby BlogAssets.Blog odkazovaly na příslušnou sledovanou již Blog instanci.
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}
Nakonec, po třetím dotazu, navigace v kolekci Blog.Posts nyní obsahují všechny související příspěvky, a Post.Blog odkazy směřují k příslušné instanci 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: []
Jedná se o stejný koncový stav, jak toho bylo dosaženo původním jednotlivým dotazem, protože EF Core upravil navigaci při sledování entit, i když entity pocházely z více různých dotazů.
Poznámka:
Oprava nikdy nezpůsobí vrácení dalších dat z databáze. Spojuje pouze entity, které již dotaz vrací nebo které již sleduje DbContext. Informace o zpracování duplicit při serializaci entit najdete v tématu Řešení identity v EF Core .
Změna relací pomocí navigace
Nejjednodušší způsob, jak změnit vztah mezi dvěma entitami, je manipulace s navigačním panelem a ponecháním EF Core, aby opravili hodnoty inverzní navigace a FK odpovídajícím způsobem. Můžete to udělat takto:
- Přidání nebo odebrání entity z navigace v kolekci
- Změna referenční navigace tak, aby odkazovala na jinou entitu nebo byla nastavena na null.
Přidávání nebo odstraňování z navigací kolekcí
Pojďme například přesunout jeden z příspěvků z blogu sady Visual Studio na blog .NET. To vyžaduje nejprve načtení blogů a příspěvků a následné přesunutí příspěvku z navigační kolekce na jednom blogu do navigační kolekce na druhém blogu:
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();
Návod
Je nutné zavolat ChangeTracker.DetectChanges(), protože přístup k zobrazení ladění nezpůsobí automatickou detekci změn.
Toto je zobrazení ladění vytištěné po spuštění výše uvedeného kódu:
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: []
Navigace Blog.Posts na blogu .NET teď obsahuje tři příspěvky (Posts: [{Id: 1}, {Id: 2}, {Id: 3}]).
Blog.Posts Stejně tak má navigace na blogu sady Visual Studio také jenom jeden příspěvek (Posts: [{Id: 4}]). To se má očekávat, protože kód tyto kolekce explicitně změnil.
Zajímavější je, že i když kód explicitně nezměnil Post.Blog navigaci, byl opraven, aby odkazoval na blog sady Visual Studio (Blog: {Id: 1}). Hodnota cizího klíče byla také aktualizována tak, Post.BlogId aby odpovídala hodnotě primárního klíče blogu .NET. Tato změna hodnoty FK se po zavolání SaveChanges uloží do databáze:
-- 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();
Změna referenčních navigací
V předchozím příkladu byl příspěvek přesunut z jednoho blogu do druhého manipulací s procházením kolekce příspěvků na každém blogu. Totéž lze dosáhnout změnou Post.Blog referenční navigace tak, aby odkazovala na nový blog. Například:
var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
post.Blog = dotNetBlog;
Zobrazení ladění po této změně je úplně stejné jako v předchozím příkladu. Důvodem je to, že EF Core zjistila změnu referenční navigace a pak opravila navigace kolekce a hodnotu FK tak, aby odpovídaly.
Změna relací pomocí hodnot cizího klíče
V předchozí části byly relace manipulovány prostřednictvím navigací, které umožňují automatickou aktualizaci hodnot cizího klíče. Toto je doporučený způsob manipulace s relacemi v EF Core. Je však také možné manipulovat s hodnotami FK přímo. Můžete například přesunout příspěvek z jednoho blogu do druhého změnou hodnoty cizího Post.BlogId klíče:
var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
post.BlogId = dotNetBlog.Id;
Všimněte si, že se to velmi podobá změně referenční navigace, jak je znázorněno v předchozím příkladu.
Zobrazení ladění po této změně je znovu přesně stejné jako v případě předchozích dvou příkladů. Důvodem je to, že EF Core zjistila změnu hodnoty FK a pak opravila jak navigaci v odkazu, tak v kolekci, aby odpovídaly.
Návod
Nepište kód pro manipulaci se všemi navigacemi a hodnotami FK při každé změně relace. Takový kód je složitější a musí zajistit konzistentní změny cizích klíčů a navigace v každém případě. Pokud je to možné, stačí manipulovat s jednou navigaci nebo možná s oběma navigacemi. V případě potřeby stačí manipulovat s hodnotami FK. Vyhněte se manipulaci s navigacemi i hodnotami FK.
Oprava přidaných nebo odstraněných entit
Přidání do kolekční navigace
EF Core provede následující akce, když zjistí , že do navigace kolekce byla přidána nová závislá/podřízená entita:
- Pokud entita není sledována, bude sledována. (Entita bude obvykle ve
Addedstavu. Pokud je však typ entity nakonfigurovaný tak, aby používal vygenerované klíče a je nastavena hodnota primárního klíče, bude entitaUnchangedsledována ve stavu.) - Pokud je entita přidružená k jinému hlavnímu nebo nadřazenému, je tento vztah přerušen.
- Entita se spřaží s hlavním objektem nebo nadřazeným objektem, který vlastní kolekční navigaci.
- Navigace a hodnoty cizího klíče jsou pevně nastaveny pro všechny zúčastněné entity.
Na základě toho vidíme, že pokud chcete přesunout příspěvek z jednoho blogu do jiného, nemusíme ho před přidáním do nového příspěvku odebrat ze staré navigace kolekce. Kód z výše uvedeného příkladu se tedy dá změnit z:
var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
vsBlog.Posts.Remove(post);
dotNetBlog.Posts.Add(post);
Do:
var post = vsBlog.Posts.Single(e => e.Title.StartsWith("Disassembly improvements"));
dotNetBlog.Posts.Add(post);
EF Core zjistí, že příspěvek byl přidán do nového blogu a automaticky ho odebere z kolekce na prvním blogu.
Odebrání z navigace v kolekci
Odebrání závislé/podřízené entity z navigace v kolekci hlavní/nadřazené entity způsobí přerušení vztahu s touto hlavní/nadřazenou entitou. Co se stane dál, závisí na tom, jestli je relace volitelná nebo povinná.
Volitelné relace
Ve výchozím nastavení pro volitelné relace je hodnota cizího klíče nastavena na hodnotu null. To znamená, že závislý/podřízený objekt už není přidružený k žádnému objektu zabezpečení nebo nadřazenosti. Načteme například blog a příspěvky a poté odstraníme jeden z příspěvků v rámci navigace kolekce:
var post = dotNetBlog.Posts.Single(e => e.Title == "Announcing F# 5");
dotNetBlog.Posts.Remove(post);
Po této změně zobrazení ladění sledování změn ukazuje, že:
- Sada
Post.BlogIdFK byla nastavena na hodnotu null (BlogId: <null> FK Modified Originally 1) - Referenční
Post.Blognavigace byla nastavena na hodnotu null (Blog: <null>) - Příspěvek byl odebrán z
Blog.Postsnavigace v kolekci (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: []
Všimněte si, že příspěvek není označen jako Deleted. Je označen jako Modified tak, aby hodnota FK v databázi byla nastavena na hodnotu null při zavolání SaveChanges.
Požadované relace
Nastavení hodnoty FK na hodnotu null není povolené (a obvykle není možné) pro požadované relace. Proto oddělení požadované relace znamená, že závislá/podřízená entita musí být buď přidělena novému nadřazenému objektu, nebo odebrána z databáze při volání funkce SaveChanges, aby se zabránilo porušení referenčního omezení. To se označuje jako "odstranění osamocených", což je výchozí chování EF Core pro požadované relace.
Pojďme například změnit vztah mezi blogem a příspěvky, které se mají vyžadovat, a pak spustit stejný kód jako v předchozím příkladu:
var post = dotNetBlog.Posts.Single(e => e.Title == "Announcing F# 5");
dotNetBlog.Posts.Remove(post);
Zobrazení ladění po této změně ukazuje, že:
- Příspěvek byl označen jako
Deletedtakový, že se po zavolání SaveChanges odstraní z databáze. - Referenční
Post.Blognavigace byla nastavena na hodnotu null (Blog: <null>). - Příspěvek byl odebrán z
Blog.Postsnavigace kolekce (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: []
Všimněte si, že Post.BlogId zůstane beze změny, protože pro požadovanou relaci nelze nastavit hodnotu null.
Volání SaveChanges vede k odstranění osiřelého příspěvku.
-- Executed DbCommand (0ms) [Parameters=[@p0='2' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Posts"
WHERE "Id" = @p0;
SELECT changes();
Odstranění osiřelých časování a přenastavení rodičů
Ve výchozím nastavení označte Deleted změny, jakmile se zjistí změna relace. Tento proces však může být zpožděn, dokud se funkce SaveChanges ve skutečnosti nevolá. To může být užitečné, pokud se chcete vyhnout vytváření osamocených entit, které byly odebrány z jednoho objektu zabezpečení nebo nadřazeného objektu, ale než je zavolána funkce SaveChanges, budou znovu přiřazené s novým objektem zabezpečení nebo nadřazeným objektem.
ChangeTracker.DeleteOrphansTiming slouží k nastavení tohoto načasování. Například:
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();
Po odebrání příspěvku z první kolekce objekt není označen jako Deleted v předchozím příkladu. Ef Core místo toho sleduje, že je relace přerušená , i když se jedná o požadovanou relaci. (Hodnota FK je považována za null EF Core, i když ve skutečnosti nemůže být null, protože typ není nullable. To se označuje jako "konceptuální 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: []
Při volání SaveChanges v tuto chvíli dojde k odstranění osamoceného příspěvku. Nicméně, pokud je příspěvek, jako v příkladu výše, přidružen k novému blogu před tím, než je volána funkce SaveChanges, pak bude přiřazen k tomuto novému blogu a již není považován za osamocený.
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: []
Funkce SaveChanges volaná v tomto okamžiku aktualizuje příspěvek v databázi místo odstranění.
Je také možné vypnout automatické odstranění osamocených objektů. Výsledkem bude výjimka, pokud bude zavolána funkce SaveChanges, zatímco je sledován osiřelý objekt. Například tento kód:
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
Vyvolá tuto výjimku:
System.InvalidOperationException: Přidružení mezi entitami Blog a Post s hodnotou klíče {BlogId: 1} bylo přerušeno, ale relace je označena jako povinná nebo je implicitně povinná, protože cizí klíč nemá hodnotu null. Pokud by závislá nebo podřízená entita měla být odstraněna v případě, že je požadovaná relace přerušena, nakonfigurujte relaci tak, aby používala kaskádové odstranění.
Odstranění osamocených i kaskádových odstranění může být vynuceno kdykoli voláním ChangeTracker.CascadeChanges(). Kombinace tohoto nastavení s nastavením časování odstranění osiřelého objektu zajistí, že osiřelé objekty se nikdy neodstraní, pokud explicitně neudělíte pokyn EF Core.
Změna referenční navigace
Změna referenční navigace ve vztahu 1:N má stejný účinek jako změna navigace kolekce na druhém konci vztahu. Nastavení referenční navigace závislé/podřízené na hodnotu null je ekvivalentní odebrání entity ze sbírkové navigace hlavního/rodičovského objektu. Všechny opravy a změny v databázi probíhají, jak je popsáno v předchozí části, včetně zneplatnění entity, pokud je relace povinná.
Volitelné relace 1:1
U relací 1:1 způsobí, že změna referenční navigace přeruší všechny předchozí vztahy. U volitelných relací to znamená, že hodnota FK u dříve související závislé/podřízené položky je nastavená na hodnotu null. Například:
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();
Zobrazení ladění před voláním SaveChanges ukazuje, že nová aktiva nahradila stávající aktiva, která jsou nyní označena jako Modified, a BlogAssets.BlogId FK má hodnotu null.
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>
Výsledkem je aktualizace a vložení při zavolání SaveChanges:
-- 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();
Požadované vztahy jedna ku jedné
Spuštění stejného kódu jako v předchozím příkladu, ale tentokrát s povinným vztahem jeden ku jednomu, ukazuje, že dříve přidružené BlogAssets je nyní označeno jako Deleted, protože se stává osamoceným, kdy nový BlogAssets zaujme jeho místo.
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>
Výsledkem je odstranění a vložení při zavolání SaveChanges:
-- 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();
Časování označení osamocených položek, které se odstraní, se dá změnit stejným způsobem jako u navigace v kolekci a má stejné účinky.
Odstranění entity
Volitelné relace
Když je entita označena jako Deleted, například voláním DbContext.Remove, pak se odkazy na odstraněnou entitu odeberou z navigací jiných entit. Pro volitelné relace jsou hodnoty FK v závislých entitách nastaveny na hodnotu null.
Pojďme například označit blog sady Visual Studio jako 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();
Zobrazení zobrazení ladění sledování změn před voláním SaveChanges:
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: []
Všimněte si, že:
- Blog je označen jako
Deleted. - Prostředky související s odstraněným blogem mají hodnotu null FK (
BlogId: <null> FK Modified Originally 2) a navigaci s odkazem na hodnotu null (Blog: <null>). - Každý příspěvek související s odstraněným blogem má hodnotu null FK (
BlogId: <null> FK Modified Originally 2) a navigaci s odkazem na hodnotu null (Blog: <null>).
Požadované relace
Chování opravy požadovaných relací je stejné jako u volitelných relací s tím rozdílem, že závislé/podřízené entity jsou označeny jako Deleted, protože nemohou existovat bez hlavního/nadřazeného objektu a musí být odebrány z databáze při volání funkce SaveChanges, aby se předešlo výjimce referenčního omezení. To se nazývá "kaskádové odstranění" a jedná se o standardní chování EF Core pro požadované vztahy. Například spuštění stejného kódu jako v předchozím příkladu, ale s požadovaným vztahem vede k následujícímu náhledu ladění předtím, než je zavolána funkce SaveChanges:
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: []
Podle očekávání jsou závislé/podřízené osoby nyní označeny jako Deleted. Všimněte si však, že navigace na odstraněných entitách se nezměnila . Může to vypadat podivně, ale vyhne se úplnému vymazání odstraněného grafu entit vymazáním všech navigačních panelů. To znamená, že blog, asset a příspěvky stále tvoří graf entit i po odstranění. Díky tomu je mnohem jednodušší obnovit odstraněný graf entit než v EF6, kde byl graf rozdělen.
Kaskádové odstranění časování a opětovného nadřazení
Ve výchozím nastavení se kaskádové odstranění provede, jakmile je nadřazený/instanční objekt označený jako Deleted. To je stejné jako při odstraňování sirotků, jak je popsáno výše. Stejně jako při odstraňování osamocených objektů může být tento proces zpožděn, dokud není zavolána funkce SaveChanges, nebo dokonce úplně vypnut, nastavením ChangeTracker.CascadeDeleteTiming. To je užitečné stejným způsobem jako při odstraňování osiřelých položek, včetně znovu přidělení dětem/závislým po odstranění hlavního/rodičovského objektu.
Kaskádové odstranění, stejně jako odstranění osamocených, mohou být vynuceny kdykoli voláním ChangeTracker.CascadeChanges(). Kombinace tohoto nastavení s určením načasování kaskádového odstranění na Never zajistí, že kaskádové odstranění se nikdy nestane, pokud k tomu EF Core není explicitně instruován.
Návod
Kaskádové odstranění a odstranění osamocených záznamů úzce souvisí. Oba mají za následek odstranění závislých/podřízených entit, pokud je vztah k jejich požadovanému hlavnímu/rodičovskému subjektu přerušen. V případě kaskádového odstranění dojde k tomuto přerušení, protože se odstraní hlavní objekt nebo nadřazený objekt. U sirotků stále existuje hlavní/nadřazená entita, ale už není ve vztahu k závislým/podřízeným entitám.
Mnoho-k-mnoho vztahy
Relace M:N v EF Core se implementují pomocí spojovací entity. Každá strana relace M:N je spojena s touto slučovací entitou s relací 1:N. Tuto entitu spojení je možné explicitně definovat a mapovat nebo ji lze vytvořit implicitně a skrýt. V obou případech je základní chování stejné. Nejprve se podíváme na toto základní chování, abychom pochopili, jak funguje sledování relací mnoho ku mnoha.
Jak fungují relace M:N
Zvažte model EF Core, který vytváří mnohočetný vztah mezi příspěvky a značkami pomocí explicitně definované entity spojení:
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
}
Všimněte si, že PostTag typ entity join obsahuje dvě vlastnosti cizího klíče. V tomto modelu musí být pro příspěvek, který má souviset se značkou, entita spojení PostTag, kde PostTag.PostId hodnota cizího klíče odpovídá hodnotě primárního Post.Id klíče a kde PostTag.TagId hodnota cizího klíče odpovídá hodnotě primárního Tag.Id klíče. Například:
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);
Zobrazení debugovacího zobrazení sledování změn po spuštění tohoto kódu ukazuje, že příspěvek a značka jsou propojeny novou PostTag propojující entitou:
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}]
Všimněte si, že navigace kolekcí na Post a Tag byly opraveny, stejně jako odkazové navigace na PostTag. Tyto relace je možné manipulovat pomocí navigace místo hodnot FK, stejně jako ve všech předchozích příkladech. Výše uvedený kód můžete například upravit, abyste přidali relaci nastavením referenčních navigací u entit propojení.
context.Add(new PostTag { Post = post, Tag = tag });
Výsledkem je přesně stejná změna sad FK a navigace jako v předchozím příkladu.
Přeskočit navigace
Ruční manipulace s tabulkou spojení může být těžkopádná. Relaci mnoho-mnoho lze přímo manipulovat pomocí speciálních navigačních operací kolekce, které obchází entitu spojení. Do výše uvedeného modelu je možné přidat například dvě přeskočení navigace; z postu na značky a druhý ze značky na příspěvky:
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
}
Tato relace M:N vyžaduje následující konfiguraci, aby bylo zajištěno, že všechny přeskočené navigace a běžné navigace jsou používány pro stejnou relaci M:N.
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));
}
Další informace o mapování mnoha k mnoha relací najdete v tématu Relace.
Navigace 'přeskočit' vypadají a fungují stejně jako normální navigace v kolekcích. Způsob práce s hodnotami cizího klíče se ale liší. Přidružíme příspěvek ke značce, ale tentokrát použijeme navigaci pro přeskočení:
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);
Všimněte si, že tento kód nepoužívá entitu join. Místo toho pouze přidá entitu do navigační kolekce stejným způsobem, jako by šlo o vztah jeden na mnoho. Výsledné zobrazení ladění je v podstatě stejné jako předtím:
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}]
Všimněte si, že instance spojovací entity PostTag byla vytvořena automaticky s hodnotami FK, které jsou nastaveny na hodnoty PK značky a příspěvku, které jsou nyní navzájem přidruženy. Všechny normální referenční a navigace kolekcí byly opraveny tak, aby odpovídaly těmto FK hodnotám. Také, protože model obsahuje skokové navigace, byly tyto opraveny. Konkrétně, i když jsme přidali značku k Post.Tags přeskočení navigace, inverzní navigace přeskočení na druhé straně tohoto vztahu byla také opravena, aby obsahovala příslušný příspěvek.
Stojí za zmínku, že základní relace M:N mohou být stále manipulovány přímo, i když nad nimi byly navrstveny přeskočené navigace. Například štítek a příspěvek by mohly být spojeny, jak tomu bylo dříve, než jsme zavedli přeskakování navigace.
context.Add(new PostTag { Post = post, Tag = tag });
Nebo pomocí hodnot FK:
context.Add(new PostTag { PostId = post.Id, TagId = tag.Id });
To povede k tomu, že se přeskočené navigace opraví správně, a výsledkem bude stejný výstup zobrazení ladění jako v předchozím příkladu.
Přeskočit pouze navigaci
V předchozí části jsme kromě plného definování dvou základních relací 1:N přidali také možnost přeskočit navigaci. To je užitečné k ilustraci toho, co se stane s hodnotami FK, ale často je zbytečné. Místo toho je možné definovat relaci M:N pouze pomocí přeskočení navigace. Toto je způsob, jakým je relace M:N definována v modelu v horní části tohoto dokumentu. Pomocí tohoto modelu můžeme znovu přidružit příspěvek a značku přidáním příspěvku do navigace pro přeskočení Tag.Posts (nebo případně přidáním značky do navigace pro přeskočení Post.Tags).
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);
Když se podíváte na zobrazení ladění po provedení této změny, zjistíte, že EF Core vytvořil objekt typu Dictionary<string, object> představující propojovací entitu. Tato entita spojení obsahuje vlastnosti cizích klíčů PostsId a TagsId, které byly nastaveny tak, aby odpovídaly hodnotám primárního klíče příspěvku a značky, které jsou přidružené.
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
Další informace o entitách implicitního spojení a použití typů entit naleznete v tématu Dictionary<string, object>.
Důležité
Typ CLR používaný pro typy entit spojení podle konvence se může v budoucích verzích změnit, aby se zlepšil výkon. Nespoléhejte se na typ spojení Dictionary<string, object>, pokud není tento typ explicitně nakonfigurován.
Spojení entit s datovými částmi
Zatím všechny příklady použily typ entity spojení (ať už explicitní nebo implicitní), který obsahuje pouze dvě vlastnosti cizího klíče potřebné pro relaci M:N. Ani jedna z těchto hodnot FK nemusí být explicitně nastavena aplikací při manipulaci s relacemi, protože jejich hodnoty pocházejí z vlastností primárního klíče souvisejících entit. To umožňuje EF Core vytvářet instance spojovací entity bez chybějících dat.
Datové prvky s vygenerovanými hodnotami
EF Core podporuje přidání dalších vlastností do typu entity pro připojení. Entita spojení má svůj "payload". Pojďme například přidat TaggedOn vlastnost do PostTag entity join:
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
}
Tato vlastnost datové části se nenastaví při vytvoření instance propojovací entity pomocí EF Core. Nejběžnější způsob, jak to vyřešit, je použít vlastnosti nákladu s automaticky generovanými hodnotami. Vlastnost lze například nakonfigurovat tak, TaggedOn aby při vložení každé nové entity používala časové razítko generované úložištěm:
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"));
}
Příspěvek je nyní možné označovat stejným způsobem jako předtím:
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);
Při zobrazení ladicího zobrazení sledovače změn po zavolání SaveChanges se ukazuje, že vlastnost datového balíčku byla správně nastavena:
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}]
Explicitní nastavení hodnot pro datovou část
Na základě předchozího příkladu přidejme vlastnost datové části, která nepoužívá automaticky generovanou hodnotu.
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
}
Příspěvek se teď dá označovat stejným způsobem jako předtím a spojovací entita bude stále vytvářena automaticky. K této entitě pak můžete přistupovat pomocí jednoho z mechanismů popsaných v části Přístup ke sledovaným entitě. Následující kód například používá DbSet<TEntity>.Find pro přístup k instanci entity join:
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);
Jakmile je entita propojení umístěna, s ní může být manipulováno běžným způsobem – v tomto případě nastavit TaggedBy vlastnost datové části před voláním SaveChanges.
Poznámka:
Všimněte si, že zde je vyžadováno volání ChangeTracker.DetectChanges() , aby EF Core mohl zjistit změnu navigační vlastnosti a vytvořit instanci entity join předtím, než Find se použije. Další informace najdete v tématu Detekce změn a oznámení .
Alternativně lze propojovací entitu výslovně vytvořit, aby připojila příspěvek ke značce. Například:
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);
Další způsob, jak nastavit data datové části, je buď přepsáním SaveChanges nebo použitím události DbContext.SavingChanges pro zpracování entit před aktualizací databáze. Například:
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);
}