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 vlastnost Post.BlogIdFK , jehož hodnota musí odpovídat Blog.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 pro Blog.Posts.
    • Blog.Posts je navigace kolekce z blogu na všechny přidružené příspěvky. Blog.Posts je inverzní navigace pro Post.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 vlastnost BlogAssets.BlogIdFK , jehož hodnota musí odpovídat Blog.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 pro Blog.Assets.
    • Blog.Assets je referenční navigace z blogu na přidružené prostředky. Blog.Assets je inverzní navigace pro BlogAssets.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 pro Tag.Posts.
    • Tag.Posts je navigace kolekce ze značky na všechny přidružené příspěvky. Tag.Posts je inverzní navigace pro Post.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 = context.Blogs
    .Include(e => e.Posts)
    .Include(e => e.Assets)
    .ToList();

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

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 = context.Blogs.Include(e => e.Posts).Single(e => e.Name == ".NET Blog");
var vsBlog = context.Blogs.Include(e => e.Posts).Single(e => e.Name == "Visual Studio Blog");

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

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

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

context.SaveChanges();

Tip

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

context.SaveChanges();

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 = 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

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

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 Deletedvolá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 = 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();

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

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

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

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

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

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

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 int SaveChanges()
{
    foreach (var entityEntry in ChangeTracker.Entries<PostTag>())
    {
        if (entityEntry.State == EntityState.Added)
        {
            entityEntry.Entity.TaggedBy = "ajcvickers";
        }
    }

    return base.SaveChanges();
}