Změna cizích klíčů a navigace
Přehled cizích klíčů a navigace
Relace v modelu Entity Framework Core (EF Core) jsou reprezentované pomocí cizích klíčů (FK). Sada FK se skládá z jedné nebo více vlastností závislé nebo podřízené entity v relaci. Tato závislá/podřízená entita je přidružená k dané hlavní/nadřazené entitě, pokud hodnoty vlastností cizího klíče závislé/podřízené závislosti odpovídají hodnotám vlastností alternativního nebo primárního klíče (PK) objektu zabezpečení nebo nadřazeného objektu.
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 .
Tip
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 .
Tip
Celý kód v tomto dokumentu můžete spustit a ladit tak, že si stáhnete ukázkový kód 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 navigace.
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):
Blog
je objekt zabezpečení nebo nadřazený objekt.Post
je závislý/podřízený. Obsahuje vlastnostPost.BlogId
FK , jehož hodnota musí odpovídatBlog.Id
hodnotě PK souvisejícího blogu.Post.Blog
je referenční navigace z příspěvku na přidružený blog.Post.Blog
je inverzní navigace proBlog.Posts
.Blog.Posts
je navigace kolekce z blogu na všechny přidružené příspěvky.Blog.Posts
je inverzní navigace proPost.Blog
.
- Každý blog může mít jeden prostředek (1:1):
Blog
je objekt zabezpečení nebo nadřazený objekt.BlogAssets
je závislý/podřízený. Obsahuje vlastnostBlogAssets.BlogId
FK , jehož hodnota musí odpovídatBlog.Id
hodnotě PK souvisejícího blogu.BlogAssets.Blog
je referenční navigace z prostředků na přidružený blog.BlogAssets.Blog
je inverzní navigace proBlog.Assets
.Blog.Assets
je referenční navigace z blogu na přidružené prostředky.Blog.Assets
je 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 M:N jsou popsány dále v tomto dokumentu.
Post.Tags
je navigace kolekce z příspěvku na všechny přidružené značky.Post.Tags
je inverzní navigace proTag.Posts
.Tag.Posts
je navigace kolekce ze značky na všechny přidružené příspěvky.Tag.Posts
je inverzní navigace proPost.Tags
.
Další informace o modelování a konfiguraci relací najdete v tématu Relace .
Oprava relace
EF Core udržuje navigaci v souladu s hodnotami cizího klíče a naopak. To znamená, že pokud se hodnota cizího klíče změní tak, aby teď odkazovat na jinou hlavní/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 relace".
Oprava podle dotazu
K opravě nejprve dojde, když se entity dotazují 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 je nastavená Blog.Assets
tak, aby ukazovala na instanci assetů a BlogAsserts.Blog
navigace je nastavená tak, aby odkazovat 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 prostředek a sleduje 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 ve výstupu výše znamená, Posts: [{Id: 1}, {Id: 2}]
že Blog.Posts
navigace v kolekci obsahuje dva související příspěvky s primárními klíči 1 a 2 v uvedeném pořadí. Podobně u každého příspěvku přidruženého k prvnímu blogu řádek označuje, Blog: {Id: 1}
že Post.Blog
navigace 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 prostředky:
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, BlogAssets.Blog
aby odkazovaly na příslušnou již sledovaný 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}
Po třetím dotazu Blog.Posts
teď navigace v kolekci obsahují všechny související příspěvky a Post.Blog
odkazy odkazují na příslušnou Blog
instanci:
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 jako u původního jediného dotazu, protože EF Core opravila navigaci jako entity, i když pocházejí 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 postupovat takto:
- Přidání nebo odebrání entity z navigace v kolekci
- Změna referenční navigace tak, aby odkazovat na jinou entitu, nebo ji nastavil na hodnotu null.
Přidání nebo odebrání z navigačních panelů kolekce
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();
Tip
Tady je potřeba volat 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
Navigace na blogu sady Visual Studio má 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 v této chvíli trvala v databázi při zavolání SaveChanges:
-- 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 navigačních panelů
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 odkazovat na nový blog. Pří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 navigacemi, které opouštějí automatické aktualizace 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 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.
Tip
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 pro přidané nebo odstraněné entity
Přidání do navigace v kolekci
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
Added
stavu. Pokud je však typ entity nakonfigurovaný tak, aby používal vygenerované klíče a je nastavena hodnota primárního klíče, bude entitaUnchanged
sledována ve stavu.) - Pokud je entita přidružená k jinému objektu zabezpečení nebo nadřazení objektu, je tento vztah přerušen.
- Entita se přidružuje k objektu zabezpečení nebo nadřazeného objektu, který vlastní navigaci v kolekci.
- 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 objektu zabezpečení nebo nadřazeného objektu způsobí přerušení vztahu s tímto objektem zabezpečení nebo nadřazeným objektem. 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 třeba blog a příspěvky a odebereme jeden z příspěvků z Blog.Posts
navigace kolekce:
var post = dotNetBlog.Posts.Single(e => e.Title == "Announcing F# 5");
dotNetBlog.Posts.Remove(post);
Zobrazení ladění sledování změn po této změně ukazuje, že:
- Sada
Post.BlogId
FK byla nastavena na hodnotu null (BlogId: <null> FK Modified Originally 1
) - Referenční
Post.Blog
navigace byla nastavena na hodnotu null (Blog: <null>
) - Příspěvek byl odebrán z
Blog.Posts
navigace 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 dělení požadované relace znamená, že závislá/podřízená entita musí být buď znovu nadřazená novému objektu zabezpečení nebo nadřazenému objektu, nebo odebrána z databáze při zavolání 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
Deleted
takový, že se po zavolání SaveChanges odstraní z databáze. - Referenční
Post.Blog
navigace byla nastavena na hodnotu null (Blog: <null>
). - Příspěvek byl odebrán z
Blog.Posts
navigace 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í osamocené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í časování osiřelých objektů a opětovného nadřazení
Ve výchozím nastavení označte osamocené osamocené změny, jakmile Deleted
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 před zavolání funkce SaveChanges bude znovu nadřazené s novým objektem zabezpečení nebo nadřazeným objektem. ChangeTracker.DeleteOrphansTiming slouží k nastavení tohoto načasování. Pří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 hodnotu 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í hodnota 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. Pokud je však jako v příkladu výše, příspěvek je přidružen k novému blogu před SaveChanges je volána, bude opraven odpovídajícím způsobem na tento nový blog 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í.
Automatické odstranění osamocených objektů je také možné vypnout. Výsledkem bude výjimka, pokud se při sledování osiřelého objektu volá SaveChanges. 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 Never
se osamocené osamocené položky neodstraní, pokud k tomu explicitně neudělíte pokyn EF Core.
Změna referenční navigace
Změna referenční navigace relace 1:N má stejný účinek jako změna navigace kolekce na druhém konci relace. Nastavení referenční navigace závislé/podřízené na hodnotu null je ekvivalentní odebrání entity z navigace kolekce objektu zabezpečení nebo nadřazeného objektu. Všechny změny oprav a databáze probíhají, jak je popsáno v předchozí části, včetně vytvoření osamocené entity v případě potřeby relace.
Volitelné relace 1:1
U relací 1:1 způsobí změna navigace odkaz všechny předchozí relace. 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. Pří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é prostředky nahradily stávající prostředky, které jsou nyní označené jako Modified
s hodnotou null BlogAssets.BlogId
FK:
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é relace 1:1
Spuštění stejného kódu jako v předchozím příkladu, ale tentokrát s požadovanou relací 1:1 ukazuje, že dříve přidružené BlogAssets
je nyní označeno jako Deleted
, protože se stane osamoceným, když BlogAssets
se nový stane jeho místem:
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 například Deleted
voláním DbContext.Remove, pak se odkazy na odstraněnou entitu odeberou z navigace 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í ladění sledování změn před voláním příkazu 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čené, protože Deleted
nemohou existovat bez objektu zabezpečení nebo nadřazeného objektu a musí být odebrány z databáze, pokud je volána funkce SaveChanges, aby nedošlo k výjimce referenčního omezení. To se označuje jako kaskádové odstranění a výchozí chování EF Core pro požadované relace. 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 zobrazení ladění před zavolání 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šší zrušit odstranění grafu entit, než tomu bylo v EF6, kdy se graf přehrával.
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í osamocených objektů, jak je popsáno výše. Stejně jako při odstraňování osamocených osamocených objektů může být tento proces zpožděn, dokud se nevolá Nebo dokonce úplně ChangeTracker.CascadeDeleteTiming nezablokuje, nastavením. To je užitečné stejným způsobem jako při odstraňování osiřelých objektů, včetně opětovného nadřazení podřízených objektů/závislých po odstranění objektu zabezpečení nebo nadřazené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 nastavením časování kaskádového odstranění zajistí, že Never
kaskádové odstranění se nikdy nestane, pokud ef Core není explicitně instruován k tomu.
Tip
Kaskádové odstranění a odstranění osamocených objektů úzce souvisí. Oba mají za následek odstranění závislých/podřízených entit, pokud je vztah k požadovanému objektu zabezpečení nebo nadřazenosti přerušen. V případě kaskádového odstranění dojde k tomuto dělení, protože se odstraní objekt zabezpečení nebo nadřazený objekt. U osamocených objektů stále existuje objekt zabezpečení nebo nadřazená entita, ale už nesouvisí se závislými/podřízenými entitami.
Relace M:N
Relace M:N v EF Core se implementují pomocí entity spojení. Každá strana relace M:N souvisí s touto entitou spojení 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í M:N.
Jak fungují relace M:N
Zvažte tento model EF Core, který vytvoří vztah M:N mezi příspěvky a značkami pomocí explicitně definovaného typu 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. Pří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í ladění sledování změn po spuštění tohoto kódu ukazuje, že příspěvek a značka souvisí s novou PostTag
entitou join:
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 v kolekci jsou zapnuté Post
a Tag
opravené, stejně jako odkazové navigace .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 a přidat relaci nastavením referenčních navigačních panelů u entity spojení:
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čení navigace
Ruční manipulace s tabulkou spojení může být těžkopádná. Relace M:N je možné manipulovat přímo pomocí speciálních navigačních panelů kolekce, které "přeskočí" 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 se zajistilo, že se všechny navigace přeskočení a normální navigace používají 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í relací M:N najdete v tématu Relace .
Přeskočení navigace vypadá a chová se jako normální navigace v kolekci. 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 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 jenom přidá entitu do navigační kolekce stejným způsobem, jako kdyby to byla relace 1:N. 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 PostTag
entity join byla vytvořena automaticky s hodnotami FK nastavenými na hodnoty PK značky a příspěvku, které jsou nyní přidruženy. Všechny normální referenční a kolekce navigace byly opraveny tak, aby odpovídaly těmto hodnotám FK. Vzhledem k tomu, že tento model obsahuje přeskočení navigace, byly také opraveny. Konkrétně, i když jsme přidali značku k Post.Tags
navigaci přeskočení, inverzní přeskočení navigace na druhé straně této relace byla také opravena tak, Tag.Posts
aby obsahovala přidružený příspěvek.
Stojí za zmínku, že základní relace M:N mohou být stále manipulovány přímo, i když byly přeskočení navigace vrstvené nahoře. Například značka a příspěvek by mohly být přidružené jako předtím, než jsme zavedli přeskočení navigace:
context.Add(new PostTag { Post = post, Tag = tag });
Nebo pomocí hodnot FK:
context.Add(new PostTag { PostId = post.Id, TagId = tag.Id });
Výsledkem bude, že se navigace přeskočení opraví správně, což vede ke stejnému výstupu zobrazení ladění jako v předchozím příkladu.
Přeskočit jenom navigace
V předchozí části jsme přidali přeskočení navigace kromě plného definování dvou základních relací 1:N. 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 k Tag.Posts
navigaci přeskočení (případně přidáním značky Post.Tags
do přeskočení navigace):
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 instanci Dictionary<string, object>
představující entitu join. Tato entita spojení obsahuje PostsId
TagsId
vlastnosti cizího klíče, které byly nastaveny tak, aby odpovídaly hodnotám PK 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í Dictionary<string, object>
typů entit naleznete v tématu Relace.
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 na typu Dictionary<string, object>
spojení, pokud není explicitně nakonfigurovaný.
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. Díky tomu MŮŽE EF Core vytvářet instance entity spojení bez chybějících dat.
Datové části s vygenerovanými hodnotami
EF Core podporuje přidání dalších vlastností do typu entity join. Říká se tomu, že entitě spojení dává datovou část. 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í, když EF Core vytvoří instanci entity join. Nejběžnější způsob, jak to vyřešit, je použít vlastnosti datové části 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 teď možné oznamovat 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í ladění sledování změn po volání SaveChanges se zobrazuje, že vlastnost datové části 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 datové části
Dále z předchozího příkladu přidáme vlastnost datové části, která nepoužívá automaticky vygenerovanou 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á oznamovat stejným způsobem jako předtím a entita join se pořád vytvoří 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 join umístěna, může být manipulována běžným způsobem - v tomto příkladu TaggedBy
nastavit datovou část vlastnost 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 entitu join vytvořit explicitně pro přidružení příspěvku ke značce. Pří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 nastavení dat datové části je přepsáním SaveChanges nebo použitím DbContext.SavingChanges události ke zpracování entit před aktualizací databáze. Pří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);
}