Dela via


Ändra främmande nycklar och navigering

Översikt över främmande nycklar och navigeringsegenskaper

Relationer i en EF Core-modell (Entity Framework Core) representeras med hjälp av externa nycklar (FK:er). En FK består av en eller flera egenskaper för den beroende eller underordnade entiteten i relationen. Den här beroende/underordnade entiteten är associerad med en viss huvud-/överordnad entitet när värdena för egenskaperna för den främmande nyckeln på beroende/underordnad matchar värdena för egenskaperna för den alternativa eller primära nyckeln (PK) på huvudentiteten/överordnad entitet.

Sekundärnycklar är ett bra sätt att lagra och manipulera relationer i databasen, men de är inte särskilt användarvänliga när du arbetar med flera relaterade entiteter i programkoden. Därför lagrar de flesta EF Core-modeller även "navigering" över FK-representationen. Navigeringar utgör C#/.NET-referenser mellan entitetsinstanser som återspeglar associationer som hittas genom att matcha främmande nyckelvärden till primära eller alternativa nyckelvärden.

Navigeringar kan användas på båda sidor av relationer, på bara ena sidan, eller inte alls, och endast lämna kvar FK-egenskapen. FK-egenskapen kan döljas genom att göra den till en skuggegenskap. Mer information om modellering av relationer finns i Relationer .

Tips/Råd

Det här dokumentet förutsätter att entitetstillstånd och grunderna i EF Core-ändringsspårning förstås. Mer information om dessa ämnen finns i Ändringsspårning i EF Core .

Tips/Råd

Du kan köra och felsöka all kod i det här dokumentet genom att ladda ned exempelkoden från GitHub.

Exempelmodell

Följande modell innehåller fyra entitetstyper med relationer mellan dem. Kommentarerna i koden anger vilka egenskaper som är främmande nycklar, primära nycklar och navigeringar.

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
}

De tre relationerna i den här modellen är:

  • Varje blogg kan ha många inlägg (en-till-många):
    • Blog är huvudansvarig/överordnad.
    • Post är beroende/underordnad. Den innehåller FK-egenskapen Post.BlogId, vars värde måste matcha Blog.Id PK-värdet för den relaterade bloggen.
    • Post.Blog är en referensnavigering från ett inlägg till den associerade bloggen. Post.Blog är inverteringsnavigering för Blog.Posts.
    • Blog.Posts är en samlingsnavigering från en blogg till alla associerade inlägg. Blog.Posts är inverteringsnavigering för Post.Blog.
  • Varje blogg kan ha en resurs (en-till-en):
    • Blog är huvudansvarig/överordnad.
    • BlogAssets är beroende/underordnad. Den innehåller FK-egenskapen BlogAssets.BlogId, vars värde måste matcha Blog.Id PK-värdet för den relaterade bloggen.
    • BlogAssets.Blog är en referensnavigering från resurserna till den tillhörande bloggen. BlogAssets.Blog är inverteringsnavigering för Blog.Assets.
    • Blog.Assets är en referensnavigering från bloggen till de associerade tillgångarna. Blog.Assets är inverteringsnavigering för BlogAssets.Blog.
  • Varje inlägg kan ha många taggar och varje tagg kan ha många inlägg (många-till-många):
    • Många-till-många-relationer är ytterligare ett lager över två en-till-många-relationer. Många-till-många-relationer beskrivs senare i det här dokumentet.
    • Post.Tags är en samlingsnavigering från ett inlägg till alla associerade taggar. Post.Tags är inverteringsnavigering för Tag.Posts.
    • Tag.Posts är en samlingsnavigering från en tagg till alla associerade inlägg. Tag.Posts är inverteringsnavigering för Post.Tags.

Mer information om hur du modellerar och konfigurerar relationer finns i Relationer .

Relationskorrigering

EF Core håller navigeringen i linje med sekundärnyckelvärden och vice versa. Om ett sekundärnyckelvärde ändras så att det nu refererar till en annan huvudentitet/överordnad entitet uppdateras navigeringarna för att återspegla denna ändring. Om en navigering ändras uppdateras också de externa nyckelvärdena för de berörda entiteterna så att de återspeglar den här ändringen. Detta kallas "relationskorrigering".

Justering efter sökfråga

Korrigering sker först när entiteter efterfrågas från databasen. Databasen har endast sekundärnyckelvärden, så när EF Core skapar en entitetsinstans från databasen använder den värdena för sekundärnyckeln för att ange referensnavigeringar och lägga till entiteter i samlingsnavigeringar efter behov. Överväg till exempel en fråga för bloggar och dess associerade inlägg och tillgångar:

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

För varje blogg skapar EF Core först en Blog instans. När varje inlägg sedan läses in från databasen är dess Post.Blog referensnavigering inställd på att peka på den associerade bloggen. På samma sätt läggs inlägget till i samlingsnavigering Blog.Posts . Samma sak händer med BlogAssets, förutom i det här fallet är båda navigeringarna referenser. Navigeringen Blog.Assets är inställd på att peka på tillgångsinstansen och navigeringen BlogAsserts.Blog är inställd på att peka på blogginstansen.

Om du tittar på felsökningsvyn för ändringsspåraren efter den här förfrågan visar den två bloggar, var och en med en tillgång och två inlägg som spåras:

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

Felsökningsvyn visar både nyckelvärden och navigeringar. Navigering visas med hjälp av de primära nyckelvärdena för de relaterade entiteterna. I utdata ovan anger till exempel Posts: [{Id: 1}, {Id: 2}] att Blog.Posts samlingsnavigering innehåller två relaterade inlägg med primärnycklarna 1 respektive 2. På samma sätt anger raden för varje inlägg som är associerat med den första bloggen Blog: {Id: 1} att navigeringen Post.Blog refererar till bloggen med primärnyckel 1.

Justering av lokalt spårade entiteter

Relationskorrigering sker också mellan entiteter som returneras från en spårningsfråga och entiteter som redan spåras av DbContext. Överväg till exempel att köra tre separata frågor för bloggar, inlägg och tillgångar:

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

Om du tittar igen på felsökningsvyerna spåras bara de två bloggarna efter den första frågan:

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

Referensnavigeringarna Blog.Assets är null och Blog.Posts samlingsnavigeringarna är tomma eftersom inga associerade entiteter för närvarande spåras av kontexten.

Efter den andra frågan har Blogs.Assets referensnavigeringarna åtgärdats så att de pekar på de nyligen spårade BlogAsset instanserna. BlogAssets.Blog På samma sätt är referensnavigeringarna inställda på att peka på lämplig redan spårad Blog instans.

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}

Efter den tredje frågan Blog.Posts innehåller samlingsnavigeringarna nu alla relaterade inlägg och referenserna Post.Blog pekar på lämplig Blog instans:

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

Det här är samma sluttillstånd som uppnåddes med den ursprungliga enskilda frågan, eftersom EF Core har korrigerat navigeringar när entiteter spårades, även när de kom från flera olika frågor.

Anmärkning

Korrigering gör aldrig att mer data returneras från databasen. Den ansluter bara entiteter som redan returneras av frågan eller som redan spåras av DbContext. Mer information om hur du hanterar dubbletter vid serialisering av entiteter finns i Identitetsmatchning i EF Core .

Ändra relationer med hjälp av navigering

Det enklaste sättet att ändra relationen mellan två entiteter är genom att ändra en navigering, samtidigt som EF Core kan åtgärda inverterad navigering och FK-värden på rätt sätt. Detta kan göras genom att:

  • Lägga till eller ta bort en entitet från en samlingsnavigering.
  • Ändra en referensnavigering så att den pekar på en annan entitet eller ange den till null.

Lägga till eller ta bort från samlingsnavigeringar

Låt oss till exempel flytta ett av inläggen från Visual Studio-bloggen till .NET-bloggen. Detta kräver att du först läser in bloggar och inlägg och sedan flyttar inlägget från navigeringssamlingen på en blogg till navigeringssamlingen på den andra bloggen:

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

Tips/Råd

Ett anrop till ChangeTracker.DetectChanges() behövs här eftersom åtkomst till felsökningsvyn inte orsakar automatisk identifiering av ändringar.

Det här är felsökningsvyn som skrivs ut när du har kört koden ovan:

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

Navigeringen Blog.Posts på .NET-bloggen har nu tre inlägg (Posts: [{Id: 1}, {Id: 2}, {Id: 3}]). På samma sätt har navigeringen Blog.Posts på Visual Studio-bloggen bara ett inlägg (Posts: [{Id: 4}]). Detta kan förväntas eftersom koden uttryckligen ändrade dessa samlingar.

Mer intressant är att även om koden inte uttryckligen ändrade navigeringen Post.Blog har den åtgärdats så att den pekar på Visual Studio-bloggen (Blog: {Id: 1}). Dessutom har värdet för sekundärnyckeln Post.BlogId uppdaterats så att det matchar det primära nyckelvärdet för .NET-bloggen. Den här ändringen av FK-värdet sparas sedan i databasen när SaveChanges anropas:

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

Ändra referensnavigeringar

I föregående exempel flyttades ett inlägg från en blogg till en annan genom att ändra samlingsnavigeringen av inlägg på varje blogg. Samma sak kan uppnås genom att i stället ändra Post.Blog referensnavigering så att den pekar på den nya bloggen. Till exempel:

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

Felsökningsvyn efter den här ändringen är exakt densamma som i föregående exempel. Det beror på att EF Core identifierade referensnavigeringsändringen och sedan fixade samlingsnavigerings- och FK-värdet för att matcha.

Ändra relationer med hjälp av främmande nyckelvärden

I föregående avsnitt manipulerades relationer genom navigeringar där främmande nyckelvärden uppdaterades automatiskt. Det här är det rekommenderade sättet att manipulera relationer i EF Core. Det går dock också att ändra FK-värden direkt. Vi kan till exempel flytta ett inlägg från en blogg till en annan genom att ändra värdet för sekundärnyckeln Post.BlogId :

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

Observera hur detta liknar att ändra referensnavigering, som du såg i föregående exempel.

Felsökningsvyn efter den här ändringen är återigen exakt densamma som för de föregående två exemplen. Det beror på att EF Core identifierade FK-värdeändringen och sedan fixade både referens- och samlingsnavigeringarna som skulle matchas.

Tips/Råd

Skriv inte kod för att ändra alla navigerings- och FK-värden varje gång en relation ändras. Sådan kod är mer komplicerad och måste säkerställa konsekventa ändringar av främmande nycklar och navigeringar i alla fall. Om möjligt kan du bara ändra en enda navigering, eller kanske båda navigeringarna. Ändra FK-värden om det behövs. Undvik att ändra både navigerings- och FK-värden.

Korrigering för tillagda eller borttagna entiteter

Lägga till i en samlingsnavigering

EF Core utför följande åtgärder när den identifierar att en ny beroende/underordnad entitet har lagts till i en samlingsnavigering:

  • Om entiteten inte spåras spåras den. (Entiteten är vanligtvis i tillståndet Added . Men om entitetstypen har konfigurerats för att använda genererade nycklar och primärnyckelvärdet har angetts spåras entiteten i Unchanged tillståndet.)
  • Om entiteten är associerad med en annan huvudansvarig/förälder, avslutas den relationen.
  • Entiteten associeras med den huvudentitet/förälder som äger navigeringen av samlingen.
  • Navigering och främmande nyckelvärden har åtgärdats för alla inblandade entiteter.

Baserat på detta kan vi se att för att flytta ett inlägg från en blogg till en annan behöver vi faktiskt inte ta bort det från det gamla samlingsnavigering innan vi lägger till det i den nya. Så koden från exemplet ovan kan ändras från:

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

Till:

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

EF Core ser att inlägget har lagts till i en ny blogg och automatiskt tar bort det från samlingen på den första bloggen.

Ta bort från en samlingsnavigering

Om du tar bort en beroende/underordnad entitet från samlingsnavigeringen av huvudentiteten/överordnad leder det till att relationen till den huvudentiteten/överordnade bryts. Vad som händer härnäst beror på om relationen är valfri eller obligatorisk.

Valfria relationer

Som standardinställning för valfria relationer är värdet för främmande nyckel inställt på null. Det innebär att beroende/barn inte längre är associerat med någon vårdnadshavare. Vi läser till exempel in en blogg och inlägg och tar sedan bort ett av inläggen från samlingsnavigering Blog.Posts :

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

Om du tittar på felsökningsvyn för ändringsspårning efter den här ändringen visas följande:

  • FK Post.BlogId :n har angetts till null (BlogId: <null> FK Modified Originally 1)
  • Referensnavigering Post.Blog har angetts till null (Blog: <null>)
  • Inlägget har tagits bort från Blog.Posts samlingsnavigering (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: []

Observera att inlägget inte har markerats som Deleted. Det markeras som Modified så att FK-värdet i databasen anges till null när SaveChanges anropas.

Nödvändiga relationer

Det är inte tillåtet att ange värdet för FK till null (och är vanligtvis inte möjligt) för nödvändiga relationer. Att bryta en nödvändig relation innebär därför att den beroende/underordnade entiteten antingen måste kopplas till en ny primär/överordnad entitet eller tas bort från databasen när SaveChanges anropas för att undvika en överträdelse av referensbegränsning. Detta kallas "radera föräldralösa objekt" och är standardbeteendet i EF Core för nödvändiga relationer.

Låt oss till exempel ändra relationen mellan blogg och inlägg så att den krävs och kör sedan samma kod som i föregående exempel:

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

Om du tittar på felsökningsvyn efter den här ändringen visas följande:

  • Inlägget har markerats som Deleted sådant att det tas bort från databasen när SaveChanges anropas.
  • Referensnavigering Post.Blog har angetts till null (Blog: <null>).
  • Inlägget har tagits bort från Blog.Posts samlingsnavigering (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: []

Observera att Post.BlogId förblir oförändrad eftersom för en obligatorisk relation kan den inte sättas till null.

Om du anropar SaveChanges tas det överblivna inlägget bort:

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

Ta bort tidpunkter för föräldralösa objekt och ändra föräldrarelationer

Som standardinställning markeras övergivna Deleted objekt så snart relationsändringen upptäcks. Den här processen kan dock fördröjas tills SaveChanges faktiskt anropas. Detta kan vara användbart för att undvika att göra föräldralösa entiteter som har tagits bort från en huvudentity/överordnad, men som kommer att återförbindas med en ny huvudentity/överordnad innan metoden SaveChanges anropas. ChangeTracker.DeleteOrphansTiming används för att ange den här tidpunkten. Till exempel:

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

Efter att ha tagit bort inlägget från den första samlingen markeras objektet inte som Deleted som i föregående exempel. I stället spårar EF Core att relationen är avskuren trots att det här är en nödvändig relation. (FK-värdet anses vara null av EF Core, även om det inte kan vara null eftersom typen inte är null. Detta kallas "konceptuell 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: []

Om du anropar SaveChanges just nu skulle det överblivna inlägget tas bort. Men om, liksom i exemplet ovan, ett inlägg är associerat med en ny blogg innan SaveChanges anropas, kommer det att kopplas rätt till den nya bloggen och anses inte längre vara föräldralöst.

Post {Id: 3} Modified
  Id: 3 PK
  BlogId: 1 FK Modified Originally 2
  Content: 'If you are focused on squeezing out the last bits of perform...'
  Title: 'Disassembly improvements for optimized managed debugging'
  Blog: {Id: 1}
  Tags: []

SaveChanges som anropas nu uppdaterar inlägget i databasen i stället för att ta bort det.

Det går också att inaktivera automatisk borttagning av överblivna. Detta kan resultera i ett undantag om SaveChanges anropas medan en föräldralös spåras. Till exempel, den här koden:

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

Utlöser det här undantaget:

System.InvalidOperationException: Associationen mellan entiteterna "Blog" och "Post" med nyckelvärdet {BlogId: 1} har brutits, men relationen är antingen markerad som obligatorisk eller krävs implicit eftersom den externa nyckeln inte kan ogiltigförklaras. Om den beroende/underordnade entiteten ska tas bort när en nödvändig relation är avsträvd konfigurerar du relationen så att den använder kaskadborttagningar.

Borttagning av föräldralösa objekt samt kaskadborttagningar kan tvingas när som helst genom att anropa ChangeTracker.CascadeChanges(). Om du kombinerar detta med att ställa in tidpunkten för borttagning av föräldralösa objekt till Never säkerställer du att föräldralösa objekt aldrig tas bort om inte EF Core uttryckligen instrueras att göra det.

Ändra en referensnavigering

Att ändra referensnavigering för en en-till-många-relation har samma effekt som att ändra samlingsnavigering i andra änden av relationen. Att ange referensnavigeringen för en beroende/underordnad till null motsvarar att ta bort entiteten från samlingsnavigeringen av huvudentiteten/förälder. Alla korrigerings- och databasändringar sker enligt beskrivningen i föregående avsnitt, inklusive att göra entiteten föräldralös om relationen är nödvändig.

Valfria en-till-en-relationer

För en-till-en-relationer innebär en ändring av en referensnavigering att alla tidigare relationer avbryts. För valfria relationer innebär det att FK-värdet för det tidigare relaterade beroende/underordnade värdet är inställt på null. Till exempel:

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

Felsökningsvyn innan du anropar SaveChanges visar att de nya tillgångarna har ersatt de befintliga tillgångarna, som nu har markerats som Modified med ett null-FK-värde BlogAssets.BlogId :

Blog {Id: 1} Unchanged
  Id: 1 PK
  Name: '.NET Blog'
  Assets: {Id: -2147482629}
  Posts: []
BlogAssets {Id: -2147482629} Added
  Id: -2147482629 PK Temporary
  Banner: <null>
  BlogId: 1 FK
  Blog: {Id: 1}
BlogAssets {Id: 1} Modified
  Id: 1 PK
  Banner: <null>
  BlogId: <null> FK Modified Originally 1
  Blog: <null>

Detta resulterar i en uppdatering och en infogning när SaveChanges anropas:

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

Obligatoriska en-till-en-relationer

Om du kör samma kod som i föregående exempel, men den här gången med en obligatorisk en-till-en-relation, visas att den tidigare associerade BlogAssets nu har markerats som Deleted, eftersom den blir överbliven när den nya BlogAssets tar plats:

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>

Detta resulterar sedan i en borttagning och en infogning när SaveChanges anropas:

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

Tidpunkten för att markera föräldralösa poster som borttagna kan ändras på samma sätt som för samlingsnavigeringar och ger samma effekter.

Ta bort en entitet

Valfria relationer

När en entitet markeras som Deleted, till exempel genom att anropa DbContext.Remove, tas referenser till den borttagna entiteten bort från navigeringen för andra entiteter. För valfria relationer är FK-värdena i beroende entiteter inställda på null.

Låt oss till exempel markera Visual Studio-bloggen som 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();

Om du tittar på felsökningsvyn för ändringsspåraren innan du anropar SaveChanges visas:

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

Observera att:

  • Bloggen är markerad som Deleted.
  • De tillgångar som är relaterade till den borttagna bloggen har ett null FK-värde (BlogId: <null> FK Modified Originally 2) och en null-referensnavigering (Blog: <null>)
  • Varje inlägg som är relaterat till den borttagna bloggen har ett null FK-värde (BlogId: <null> FK Modified Originally 2) och en nullreferensnavigering (Blog: <null>)

Nödvändiga relationer

Korrigeringsbeteendet för nödvändiga relationer är detsamma som för valfria relationer, förutom att beroende/underordnade entiteter markeras som Deleted eftersom de inte kan existera utan en huvudentitet/förälder och måste tas bort från databasen när SaveChanges anropas för att undvika ett undantag för referensbegränsning. Detta kallas "kaskadborttagning" och är standardbeteendet i EF Core för nödvändiga relationer. Om du till exempel kör samma kod som i föregående exempel, men med en obligatorisk relation, resulterar det i följande felsökningsvy innan SaveChanges anropas:

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

Som förväntat markeras de beroende/underordnade nu som Deleted. Observera dock att navigeringarna på de borttagna entiteterna inte har ändrats. Detta kan verka konstigt, men det undviker att helt strimla en borttagen graf över entiteter genom att rensa alla navigeringar. Det vill: bloggen, tillgången och inläggen utgör fortfarande ett diagram över entiteter även efter att ha tagits bort. Det gör det mycket enklare att ta bort ett diagram över entiteter än i EF6 där diagrammet strimlades.

Tidpunkt för kaskadborttagning och återföräldring

Som standard sker kaskadborttagning så snart överordnad/huvudnamn har markerats som Deleted. Det här är samma sak som för att ta bort överblivna objekt, enligt beskrivningen tidigare. Precis som när du tar bort föräldralösa objekt kan den här processen fördröjas tills SaveChanges anropas, eller till och med inaktiveras helt, genom att konfigurera ChangeTracker.CascadeDeleteTiming på rätt sätt. Detta är användbart på samma sätt som för att ta bort föräldralösa objekt, inklusive för beroende objekt efter borttagning av ett huvudobjekt/överordnat objekt.

Kaskadborttagningar och borttagning av överblivna objekt kan när som helst tvingas genom att anropa ChangeTracker.CascadeChanges(). Om du kombinerar detta med att ställa in kaskadborttagningstidpunkten till ser du till att Never kaskadborttagningar aldrig sker om inte EF Core uttryckligen instrueras att göra det.

Tips/Råd

Kaskadborttagning och borttagning av överblivna är nära relaterade. Båda resulterar i att beroende/underordnade entiteter tas bort när relationen till deras obligatoriska huvudentitet/överordnade avbryts. För kaskadarkadering sker detta eftersom huvudenheten/förälder själv tas bort. För föräldralösa entiteter finns fortfarande huvudförälderentiteten kvar, men den är inte längre relaterad till de beroende/barnentiteterna.

Många-till-många-relationer

Många-till-många-relationer i EF Core implementeras med hjälp av en kopplingsentitet. Varje sida av många-till-många-relationen är relaterad till denna bindande enhet med en en-till-många-relation. Den här kopplingsentiteten kan definieras och mappas explicit, eller så kan den skapas implicit och döljas. I båda fallen är det underliggande beteendet detsamma. Vi tittar först på det här underliggande beteendet för att förstå hur spårning av många-till-många-relationer fungerar.

Hur många-till-många-relationer fungerar

Tänk på den här EF Core-modellen som skapar en många-till-många-relation mellan inlägg och taggar med en explicit definierad kopplingsentitetstyp:

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
}

Observera att entitetstypen PostTag innehåller två främmande nyckel egenskaper. För att ett inlägg ska vara relaterat till en tagg i den här modellen måste det finnas en PostTag-kopplingsentitet där värdet för sekundärnyckeln PostTag.PostId matchar det Post.Id primära nyckelvärdet och där värdet för sekundärnyckeln PostTag.TagId matchar det Tag.Id primära nyckelvärdet. Till exempel:

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

Om du tittar på felsökningsvyn för ändringsspåraren när du har kört den här koden visas att inlägget och taggen är relaterade till den nya PostTag kopplingsentiteten:

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

Observera att samlingsnavigeringarna på Post och Tag har åtgärdats, liksom referensnavigeringarna på PostTag. Dessa relationer kan manipuleras genom navigering i stället för FK-värden, precis som i alla föregående exempel. Koden ovan kan till exempel ändras för att lägga till relationen genom att ange referensnavigeringarna på kopplingsentiteten:

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

Detta resulterar i exakt samma ändring av FK:er och navigeringar som i föregående exempel.

Hoppa över navigering

Det kan vara besvärligt att ändra kopplingstabellen manuellt. Många-till-många-relationer kan manipuleras direkt med hjälp av särskilda samlingsnavigeringar som "hoppar över" kopplingsentiteten. Till exempel kan två hoppa över navigeringar läggas till i modellen ovan; en från Post till Taggar, och den andra från Taggar till Inlägg.

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
}

Den här många-till-många-relationen kräver följande konfiguration för att säkerställa att hoppa över navigeringer och normala navigeringer används för samma många-till-många-relation:

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

Mer information om hur du mappar många-till-många-relationer finns i Relationer .

Hopplänkar ser ut och fungerar som vanliga samlingsnavigeringar. Hur de fungerar med främmande nyckelvärden är dock annorlunda. Nu ska vi associera ett inlägg med en tagg, men den här gången med hoppa-navigering.

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

Observera att den här koden inte använder kopplingsentiteten. I stället lägger den bara till en entitet i en navigeringssamling på samma sätt som om det här var en en-till-många-relation. Den resulterande felsökningsvyn är i stort sett densamma som tidigare:

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

Observera att en instans av "join-entiteten" PostTag skapades automatiskt med FK-värden inställda på PK-värdena för den tagg och det inlägg som nu är associerade. Alla normala referens- och samlingsnavigeringar har åtgärdats så att de matchar dessa FK-värden. Eftersom den här modellen innehåller hoppa över navigeringer har dessa också åtgärdats. Även om vi har lagt till taggen Post.Tags i hoppa över navigeringen har den Tag.Posts inverterade hoppa över navigeringen på andra sidan av den här relationen också åtgärdats så att den innehåller det associerade inlägget.

Det är värt att notera att de underliggande många-till-många-relationerna fortfarande kan manipuleras direkt även när hoppa över navigeringer har lagts ovanpå. Till exempel kan taggen och post associeras som vi gjorde innan vi introducerade hoppa-över-navigeringar.

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

Eller använda FK-värden:

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

Detta kommer fortfarande att leda till att funktionerna för att hoppa över navigeringar åtgärdas korrekt, vilket resulterar i samma utdatakälla för felsökningsvyn som i det föregående exemplet.

Hoppa endast över navigering

I föregående avsnitt lade vi till hoppa över navigeringer utöver att helt definiera de två underliggande en-till-många-relationerna. Detta är användbart för att illustrera vad som händer med FK-värden, men är ofta onödigt. I stället kan många-till-många-relationen definieras med bara hoppa över navigering. Så här definieras många-till-många-relationen i modellen högst upp i det här dokumentet. Med den här modellen kan vi återigen associera ett inlägg och en tagg genom att lägga till ett inlägg i hoppa över navigeringen Tag.Posts (eller, alternativt, lägga till en tagg i hoppa över navigeringen 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);

När du tittar på felsökningsvyn när du har gjort den här ändringen visas att EF Core har skapat en instans av Dictionary<string, object> för att representera kopplingsentiteten. Den här kopplingsentiteten innehåller både PostsId och TagsId sekundärnyckelegenskaper som har ställts in för att matcha PK-värdena för inlägget och taggen som är associerade.

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

Mer information om implicita kopplingsentiteter och användning av entitetstyper finns i Dictionary<string, object>.

Viktigt!

CLR-typen som används för att sammanfoga enhetstyper enligt konvention kan ändras i framtida versioner för att förbättra prestanda. Var inte beroende av att kopplingstypen är Dictionary<string, object> såvida inte detta har konfigurerats uttryckligen.

Anslut entiteter med nyttolaster

Hittills har alla exempel använt en kopplingsentitetstyp (antingen explicit eller implicit) som endast innehåller de två sekundärnyckelegenskaper som behövs för många-till-många-relationen. Inget av dessa FK-värden behöver uttryckligen anges av programmet när du manipulerar relationer eftersom deras värden kommer från de primära nyckelegenskaperna för de relaterade entiteterna. På så sätt kan EF Core skapa instanser av entiteten join utan att data saknas.

Nyttolaster med genererade värden

EF Core har stöd för att lägga till ytterligare egenskaper i anslutningsentitetstypen. Detta kallas att ge en "nyttolast" till kopplingsentiteten. Låt oss till exempel lägga till TaggedOn egenskapen i kopplingsentiteten PostTag :

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
}

Egenskapen för nyttolast kommer inte att anges när EF Core skapar en kopplingsentitet. Det vanligaste sättet att hantera detta är att använda nyttolastegenskaper med automatiskt genererade värden. För exempel kan egenskapen TaggedOn konfigureras för att använda en lagringsgenererad tidsstämpel när varje ny entitet infogas.

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

Ett inlägg kan nu taggas på samma sätt som tidigare:

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

Om du tittar på felsökningsvyn för ändringsspåraren när du har anropat SaveChanges visas att nyttolastegenskapen har angetts på rätt sätt:

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

Ange uttryckligen nyttolastvärden

Efter det föregående exemplet ska vi lägga till en nyttolastegenskap som inte använder ett automatiskt genererat värde:

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
}

Ett inlägg kan nu taggas på samma sätt som tidigare, och kopplingsentiteten skapas fortfarande automatiskt. Den här entiteten kan sedan nås med någon av de mekanismer som beskrivs i Åtkomst till spårade entiteter. Koden nedan använder DbSet<TEntity>.Find till exempel för att komma åt entitetsinstansen 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);

När kopplingsentiteten har hittats kan den ändras på det normala sättet– i det här exemplet för att ange nyttolastegenskapen TaggedBy innan du anropar SaveChanges.

Anmärkning

Observera att ett anrop till ChangeTracker.DetectChanges() krävs här för att ge EF Core en chans att identifiera ändringen av navigeringsegenskapen och skapa entitetsinstansen join innan Find den används. Mer information finns i Ändringsidentifiering och Meddelanden .

Alternativt kan kopplingsentiteten skapas explicit för att associera ett inlägg med en tagg. Till exempel:

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

Slutligen är ett annat sätt att ange nyttolastdata att antingen åsidosätta SaveChanges eller använda DbContext.SavingChanges-händelsen för att bearbeta entiteter innan databasen uppdateras. Till exempel:

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