Idegen kulcsok és navigációk módosítása

Idegen kulcsok és navigációk áttekintése

Az Entity Framework Core-modellben (EF Core) lévő kapcsolatok idegen kulcsokkal (FK-kkal) jelennek meg. Az FK egy vagy több tulajdonságból áll, amelyek a kapcsolat függő vagy gyermek entitásán belül találhatók. Ez a függő/gyermek entitás egy adott fő/szülő entitáshoz van társítva, ha a függő/gyermek idegenkulcs-tulajdonságainak értékei megegyeznek a fő/szülő másodlagos vagy elsődleges kulcs (PK) tulajdonságainak értékeivel.

A külföldi kulcsok jó módja a kapcsolatok tárolásának és kezelésének az adatbázisban, de nem túl barátságosak, ha több kapcsolódó entitással dolgoznak az alkalmazáskódban. Ezért a legtöbb EF Core-modell "navigációkat" is rétegez az FK-ábrázolás fölé. A navigációk C#/.NET hivatkozásokat hoznak létre az entitáspéldányok között azzal a céllal, hogy tükrözzék azokat az társításokat, amelyeket úgy találtak meg, hogy az idegen kulcsértékeket az elsődleges vagy alternatív kulcsértékekkel egyeztették.

A navigációk a kapcsolat mindkét oldalán használhatók, csak az egyik oldalon, vagy egyáltalán nem, így csak az FK tulajdonság marad. Az FK tulajdonság elrejthető árnyéktulajdonságként. A kapcsolatok modellezésével kapcsolatos további információkért tekintse meg a Kapcsolatok című témakört.

Jótanács

Ez a dokumentum feltételezi, hogy az entitásállapotok és az EF Core-változáskövetés alapjai érthetőek. Ezekről a témakörökről további információt az EF Core változáskövetési funkciójában talál.

Jótanács

A mintakód GitHubról való letöltésével futtathatja és hibakeresést végezhet a dokumentum összes kódjában.

Példamodell

Az alábbi modell négy entitástípust tartalmaz, amelyek között kapcsolat van. A kód megjegyzései jelzik, hogy mely tulajdonságok az idegen kulcsok, az elsődleges kulcsok és a navigációs tulajdonságok.

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
}

A modell három kapcsolata a következő:

  • Minden blog számos bejegyzést tartalmazhat (egy-a-többhöz):
    • Blog a fő (vezető) vagy szülő.
    • Post a függő/gyermek. Tartalmazza az FK tulajdonságot Post.BlogId, amelynek értéke meg kell egyeznie a Blog.Id kapcsolódó blog PK-értékével.
    • Post.Blog egy hivatkozási navigáció egy bejegyzésről a kapcsolódó blogra. Post.Blog a(z) Blog.Posts inverz navigációja.
    • Blog.Posts egy gyűjtemény navigációja egy blogról az összes kapcsolódó bejegyzésre. Blog.Posts a(z) Post.Blog inverz navigációja.
  • Minden bloghoz tartozhat egy eszköz (egy-az-egyhez kapcsolat).
    • Blog a fő (vezető) vagy szülő.
    • BlogAssets a függő/gyermek. Tartalmazza az FK tulajdonságot BlogAssets.BlogId, amelynek értéke meg kell egyeznie a Blog.Id kapcsolódó blog PK-értékével.
    • BlogAssets.Blog az eszközökről a kapcsolódó blogra mutató hivatkozási navigáció. BlogAssets.Blog a(z) Blog.Assets inverz navigációja.
    • Blog.Assets a blogból a kapcsolódó eszközökre mutató hivatkozási navigáció. Blog.Assets a(z) BlogAssets.Blog inverz navigációja.
  • Minden bejegyzés több címkével is rendelkezhet, és minden címkének több bejegyzése is lehet (több-a-többhöz).
    • A több-többhöz kapcsolatok további réteget jelentenek két egy-többhöz kapcsolat fölött. A dokumentum későbbi részében lesz szó a több-a-többhöz kapcsolatokról.
    • Post.Tags egy gyűjtemény navigációja egy bejegyzésből az összes társított címkére. Post.Tags a(z) Tag.Posts inverz navigációja.
    • Tag.Posts egy gyűjtemény navigációja egy címkéről az összes kapcsolódó bejegyzésre. Tag.Posts a(z) Post.Tags inverz navigációja.

A kapcsolatok modellezéséről és konfigurálásáról további információt a Kapcsolatok című témakörben talál.

Kapcsolat javítása

Az EF Core az idegen kulcsértékekkel összhangban tartja a navigációt, és fordítva. Ez azt jelenti, hogy ha egy idegenkulcs-érték úgy változik, hogy most egy másik fő/szülő entitásra hivatkozik, akkor a navigációs sávok frissülnek, hogy tükrözzék ezt a változást. Hasonlóképpen, ha egy navigáció módosul, akkor az érintett entitások idegenkulcs-értékei is frissülnek, hogy tükrözzék ezt a változást. Ezt "kapcsolatjavításnak" nevezzük.

Javítás lekérdezéssel

A javítás először akkor fordul elő, ha az entitások lekérdezhetők az adatbázisból. Az adatbázis csak idegen kulcsértékekkel rendelkezik, ezért amikor az EF Core létrehoz egy entitáspéldányt az adatbázisból, az idegenkulcs-értékekkel beállítja a referencianavigációkat, és szükség szerint entitásokat ad hozzá a gyűjteménynavigációkhoz. Fontolja meg például a blogok és a kapcsolódó bejegyzések és objektumok lekérdezését:

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

Minden blog esetében az EF Core először létrehoz egy példányt Blog . Ezután, ahogy minden bejegyzés betöltődik az adatbázisból, a Post.Blog hivatkozás navigációja a társított blogra mutat. Hasonlóképpen, a bejegyzés hozzá lesz adva a Blog.Posts gyűjtemény navigációs sávjához. Ugyanez történik is a következővel BlogAssets, kivéve, hogy ebben az esetben mindkét navigáció hivatkozásként szerepel. A Blog.Assets navigáció az eszközpéldányra, a BlogAsserts.Blog navigáció pedig a blogpéldányra mutat.

A változáskövető hibakeresési nézetét tekintve a lekérdezés után két blog látható, mindegyikben egy-egy objektum és két bejegyzés van nyomon követve:

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

A hibakeresési nézet a fő értékeket és a navigációkat is megjeleníti. A navigációs elemek a kapcsolódó entitások elsődleges kulcsértékeivel jelennek meg. A fenti kimenetben például az látható, Posts: [{Id: 1}, {Id: 2}] hogy a Blog.Posts gyűjtemény navigációs sávja két kapcsolódó bejegyzést tartalmaz, amelyek elsődleges kulcsa 1, illetve 2. Hasonlóképpen, az első bloghoz társított összes bejegyzés esetében a Blog: {Id: 1} sor azt jelzi, hogy a navigáció a Post.Blog blogra hivatkozik az 1. elsődleges kulccsal.

Helyileg nyomon követett entitások finomhangolása

A kapcsolat javítása a nyomkövetési lekérdezésből visszaadott entitások és a DbContext által már nyomon követett entitások között is megtörténik. Fontolja meg például három külön lekérdezés végrehajtását blogokhoz, bejegyzésekhez és objektumokhoz:

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

A hibakeresési nézetek ismételt megtekintése után az első lekérdezés után csak a két blogot követi nyomon a rendszer:

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

A Blog.Assets referencianavigációk null értékűek, és a Blog.Posts gyűjteménynavigációk üresek, mivel a környezet jelenleg nem követ nyomon társított entitásokat.

A második lekérdezés után a Blogs.Assets hivatkozás navigációkat kiigazították, hogy az újonnan nyomon követett BlogAsset példányokra mutassanak. Hasonlóképpen, a referenciák navigációi úgy vannak beállítva, hogy a megfelelő, már nyomon követett BlogAssets.Blog példányra mutassanak.

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}

Végül a harmadik lekérdezés után a Blog.Posts gyűjtemény navigációi mostantól az összes kapcsolódó bejegyzést tartalmazzák, és a Post.Blog hivatkozások a megfelelő Blog példányra mutatnak:

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

Ez ugyanaz a végállapot, mint az eredeti egyetlen lekérdezés esetében, mivel az EF Core rögzítette az entitások által követett navigációkat, még akkor is, ha több különböző lekérdezésből származik.

Megjegyzés:

A javítás soha nem okoz több adat visszaadását az adatbázisból. Csak a lekérdezés által visszaadott vagy a DbContext által már nyomon követett entitásokat köti össze. Az entitások szerializálása során a duplikált elemek kezelésével kapcsolatos információkért lásd az EF Core identitásfeloldása című témakört.

Kapcsolatok módosítása navigációkkal

A legegyszerűbb módja annak, hogy módosítsa a két entitás közötti kapcsolatot, ha egy navigációs tulajdonságot módosít, miközben az EF Core végzi el az inverz navigáció és az FK-értékek megfelelő javítását. Ezt a következő módon teheti meg:

  • Entitás hozzáadása vagy eltávolítása gyűjteménynavigációból.
  • Referencianavigáció módosítása egy másik entitásra mutatva vagy null értékre állítása.

Gyűjteménynavigációk hozzáadása vagy eltávolítása

Tegyük fel például, hogy áthelyezzük az egyik bejegyzést a Visual Studio blogból a .NET-blogba. Ehhez először be kell tölteni a blogokat és a bejegyzéseket, majd át kell áthelyezni a bejegyzést az egyik blog navigációs gyűjteményéből a másik blog navigációs gyűjteményére:

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

Jótanács

Erre azért van szükség, ChangeTracker.DetectChanges() mert a hibakeresési nézet elérése nem okozza a módosítások automatikus észlelését.

A fenti kód futtatása után kinyomtatott hibakeresési nézet:

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

A Blog.Posts .NET blog navigációs sávján most három bejegyzés (Posts: [{Id: 1}, {Id: 2}, {Id: 3}]) található. Hasonlóképpen, a Blog.Posts Visual Studio blog navigációs sávján csak egy bejegyzés található (Posts: [{Id: 4}]). Erre azért van szükség, mert a kód explicit módon módosította ezeket a gyűjteményeket.

Még érdekesebb, annak ellenére, hogy a kód nem változtatta meg explicit módon a Post.Blog navigációt, kijavítottuk, hogy a Visual Studio blogjára (Blog: {Id: 1}) mutasson. Emellett az Post.BlogId idegen kulcs értéke is frissült, hogy megfeleljen a .NET-blog elsődleges kulcsértékének. Az FK-érték módosítása ezután megmarad az adatbázisban a SaveChanges meghívásakor:

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

Referencianavigációk módosítása

Az előző példában egy bejegyzést áthelyeztünk az egyik blogról a másikra az egyes blogok bejegyzéseinek gyűjteménynavigációjának módosításával. Ugyanez úgy érhető el, hogy a Post.Blog referencianavigációt úgy módosítja, hogy az az új blogra mutasson. Például:

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

A módosítás utáni hibakeresési nézet pontosan ugyanaz , mint az előző példában. Ennek az az oka, hogy az EF Core észlelte a referencianavigáció módosítását, majd kijavította a gyűjteménynavigációkat és az FK-értéket az egyezéshez.

Kapcsolatok módosítása idegen kulcsértékekkel

Az előző szakaszban a kapcsolatokat navigációs műveletekkel módosították, amelyek az idegen kulcsértékeket automatikusan frissítették. Ez a javasolt módszer a kapcsolatok EF Core-ban történő módosítására. Az FK-értékek azonban közvetlenül is módosíthatók. Például áthelyezhetünk egy bejegyzést az egyik blogból a másikba az idegen kulcs értékének Post.BlogId módosításával:

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

Figyelje meg, hogy ez nagyon hasonlít a referencianavigáció módosításához, ahogy az előző példában is látható.

A módosítás utáni hibakeresési nézet ismét pontosan ugyanaz , mint az előző két példa esetében. Ennek az az oka, hogy az EF Core észlelte az FK-érték módosítását, majd kijavította a referencia- és gyűjteménynavigációkat is, hogy megfeleljenek.

Jótanács

Ne írjon kódot az összes navigációs és FK-érték módosításához minden alkalommal, amikor egy kapcsolat megváltozik. Az ilyen kód bonyolultabb, és minden esetben biztosítania kell az idegen kulcsok és navigációk konzisztens módosítását. Ha lehetséges, csak egyetlen navigációt, vagy akár mindkét navigációt is módosíthatja. Ha szükséges, csak módosítsa az FK-értékeket. Ne módosítsa a navigációs és az FK-értékeket.

A hozzáadott vagy törölt entitások javítása

Hozzáadás gyűjteménynavigációhoz

Az EF Core a következő műveleteket hajtja végre, amikor azt észleli , hogy egy új függő/gyermek entitás lett hozzáadva egy gyűjteménynavigációhoz:

  • Ha az entitás nincs nyomon követve, akkor az nyomon lesz követve. (Az entitás általában az Added állapotban lesz. Ha azonban az entitás típusa generált kulcsok használatára van konfigurálva, és az elsődleges kulcs értéke be van állítva, akkor az entitás az állapotban Unchanged lesz nyomon követve.)
  • Ha az entitás egy másik fő/szülőhöz van társítva, akkor a kapcsolat megszakad.
  • Az entitás a gyűjteménynavigációt birtoklő taghoz/szülőhöz lesz társítva.
  • A navigációs és idegenkulcs-értékek az összes érintett entitás esetében ki vannak javítva.

Ennek alapján láthatjuk, hogy egy bejegyzés egyik blogból a másikba való áthelyezéséhez valójában nem kell eltávolítani a régi gyűjtemény navigációs sávjából, mielőtt hozzáadnánk az újhoz. A fenti példában szereplő kód tehát a következőről módosítható:

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

Címzett:

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

Ef Core látja, hogy a bejegyzést hozzáadták egy új bloghoz, és automatikusan eltávolítja azt az első blog gyűjteményéből.

Eltávolítás gyűjteménynavigációból

Ha eltávolít egy függő/gyermek entitást a főentitás/szülő kollekció kezeléséből, ezzel megszakítja a kapcsolatot az adott főentitással/szülővel. A következő lépés attól függ, hogy a kapcsolat opcionális vagy kötelező.

Választható kapcsolatok

Az opcionális kapcsolatok esetében alapértelmezés szerint az idegen kulcs értéke null értékű. Ez azt jelenti, hogy a függő/gyermek már nincs társítva egyik taggal/szülővel sem. Töltsünk be például egy blogot és bejegyzést, majd távolítsuk el az egyik bejegyzést a Blog.Posts gyűjtemény navigációs sávjából:

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

A módosítás utáni változáskövetési hibakeresési nézet a következőt mutatja:

  • Az Post.BlogId FK értéke null (BlogId: <null> FK Modified Originally 1)
  • A Post.Blog referencianavigáció null értékűre lett állítva (Blog: <null>)
  • A bejegyzés el lett távolítva a gyűjtemény navigációs sávjából Blog.Posts (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: []

Figyelje meg, hogy a bejegyzés nincs megjelölve Deleted. A rendszer úgy van megjelölve Modified , hogy az adatbázis FK-értéke null értékű legyen a SaveChanges meghívásakor.

Szükséges kapcsolatok

Az FK-érték null értékre állítása nem engedélyezett (és általában nem lehetséges) a szükséges kapcsolatokhoz. Ezért a szükséges kapcsolat megszakítása azt jelenti, hogy a függő/gyermek entitást új szülőhöz kell társítani, vagy el kell távolítani az adatbázisból, amikor a SaveChanges függvényt meghívják, a hivatkozási korlátozás megsértésének elkerülése érdekében. Ez az úgynevezett "árvák törlése", és az EF Core alapértelmezett viselkedése a szükséges kapcsolatokhoz.

Változtassuk meg például a szükséges blogok és bejegyzések közötti kapcsolatot, majd futtassuk ugyanazt a kódot, mint az előző példában:

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

A hibakeresési nézet a módosítás után a következőt mutatja:

  • A bejegyzést úgy jelöltük Deleted meg, hogy a SaveChanges meghívásakor törlődik az adatbázisból.
  • A Post.Blog referencianavigáció null (Blog: <null>) értékre van állítva.
  • A bejegyzés el lett távolítva a gyűjtemény navigációs sávjából Blog.Posts (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: []

Figyelje meg, hogy a Post.BlogId kapcsolat változatlan marad, mivel egy szükséges kapcsolat esetében nem állítható null értékre.

A SaveChanges hívása az árva bejegyzés törlését eredményezi:

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

Árvák időzítésének törlése és újranevelés

Alapértelmezés szerint az árvák megjelölése Deleted a kapcsolatváltozás észlelése után azonnal megtörténik. Ez a folyamat azonban késleltethető, amíg a SaveChanges meghívása meg nem történik. Ez hasznos lehet annak elkerülése érdekében, hogy ne váljanak árvává az entitások, amelyek el lettek távolítva az egyik központi/szülő entitásból, de a SaveChanges meghívása előtt új központi/szülő entitáshoz lesznek átszervezve. ChangeTracker.DeleteOrphansTiming az időzítés beállítására szolgál. Például:

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

Miután eltávolította a bejegyzést az első gyűjteményből, az objektum nem úgy van megjelölve, mint Deleted az előző példában. Ehelyett az EF Core nyomon követi, hogy a kapcsolat megszakad , annak ellenére, hogy ez egy szükséges kapcsolat. (Az FK-értéket az EF Core null értékűnek tekinti, annak ellenére, hogy valójában nem lehet null értékű, mert a típus nem null értékű. Ezt "fogalmi nullnak" is nevezik.)

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

Ha ebben az időben meghívnánk a SaveChanges-t, az az árva bejegyzés törlését eredményezné. Ha azonban a fenti példához hasonlóan a bejegyzés egy új bloghoz van társítva a SaveChanges meghívása előtt, akkor az megfelelően lesz javítva az új blogon, és már nem minősül árvának:

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

Az ezen a ponton hívott SaveChanges a törlés helyett frissíti a bejegyzést az adatbázisban.

Az árvák automatikus törlését is kikapcsolhatja. Ez kivételt eredményez, ha a Rendszer meghívja a SaveChanges-et egy árva nyomon követése közben. Például ez a kód:

var dotNetBlog = await context.Blogs.Include(e => e.Posts).SingleAsync(e => e.Name == ".NET Blog");

context.ChangeTracker.DeleteOrphansTiming = CascadeTiming.Never;

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

await context.SaveChangesAsync(); // Throws

Ez a kivétel a következő:

System.InvalidOperationException: A(z) "Blog" és a "Post" entitások ({BlogId: 1}) kulcsértékkel való társítása megszakadt, de a kapcsolat kötelezőként van megjelölve, vagy implicit módon szükséges, mert az idegen kulcs nem null értékű. Ha a függő/gyermek entitást törölni kell egy szükséges kapcsolat megszakadásakor, konfigurálja a kapcsolatot kaszkádolt törlések használatára.

Az árvák törlését, valamint a kaszkádolt törléseket bármikor kényszeríteni lehet a ChangeTracker.CascadeChanges() hívásával. Ha ezt kombinálja az árva törlése időzítésének beállításával, az biztosítja, hogy Never az árvák soha ne legyenek törölve, kivéve, ha az EF Core kifejezetten erre utasítja.

Referencianavigáció módosítása

Az egy-a-többhöz kapcsolatok hivatkozási navigációjának módosítása ugyanolyan hatással van, mint a gyűjtemény navigációjának módosítása a kapcsolat másik végén. A függő/gyermek referencianavigációjának null értékűre állítása egyenértékű azzal, ha eltávolítja az entitást a fő/szülő gyűjteményben való navigációból. Az összes javítás és adatbázis-módosítás az előző szakaszban leírtak szerint történik, beleértve az entitás árvává tételét is, ha a kapcsolat szükséges.

Opcionális egy-az-egyhez kapcsolatok

Az egy-az-egyhez kapcsolatok esetében a referencianavigáció módosítása esetén a korábbi kapcsolatok megszakadnak. Választható kapcsolatok esetén ez azt jelenti, hogy a korábban kapcsolódó függő/gyermek FK értéke null értékű. Például:

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

A SaveChanges hívása előtti hibakeresési nézet azt mutatja, hogy az új eszközök lecserélték a meglévő objektumokat, amelyek most null Modified FK értékkel vannak megjelölveBlogAssets.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>

Ez egy frissítést és egy beszúrást eredményez a SaveChanges meghívásakor:

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

Kötelező egy-az-egyhez kapcsolatok

Ha ugyanazt a kódot futtatja, mint az előző példában, de ezúttal kötelező egy-az-egyhez kapcsolattal, akkor az azt mutatja, hogy a korábban társított BlogAssets most árva Deleted-ként van megjelölve, mivel az új BlogAssets az előző helyére lép.

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>

Ez a művelet törlést és beszúrást eredményez a SaveChanges meghívásakor:

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

Az árvák töröltként való megjelölésének időzítése ugyanúgy módosítható, mint a gyűjtemény-navigációk esetében, és ugyanolyan hatással van rá.

Entitás törlése

Választható kapcsolatok

Amikor egy entitás Deleted-ként van megjelölve, például egy DbContext.Remove hívással, akkor a törölt entitásra mutató hivatkozásokat eltávolítják más entitások navigációiból. Választható kapcsolatok esetén a függő entitások FK-értékei null értékre vannak állítva.

Jelöljük meg például a Visual Studio blogot a következőként 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();

A SaveChanges meghívása előtt tekintse meg a változáskövető hibakeresési nézetet :

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

Figyelje meg az alábbiakat:

  • A blog a következőként Deletedvan megjelölve: .
  • A törölt bloghoz kapcsolódó objektumok null FK értékkel () és nullhivatkozási navigációval (BlogId: <null> FK Modified Originally 2Blog: <null>) rendelkeznek
  • A törölt bloghoz kapcsolódó összes bejegyzés null FK értékkel (BlogId: <null> FK Modified Originally 2) és nullhivatkozási navigációval (Blog: <null>) rendelkezik

Szükséges kapcsolatok

A szükséges kapcsolatok kijavítási viselkedése ugyanaz, mint az opcionális kapcsolatok esetében, azzal a kivétellel, hogy a függő/gyermek entitások jelölése Deleted, mert nem létezhetnek principális/szülő nélkül, és el kell távolítani őket az adatbázisból, amikor a SaveChanges hívása megtörténik, ezzel elkerülve a hivatkozási kényszer kivételét. Ez az úgynevezett "kaszkádolt törlés", és az EF Core alapértelmezett viselkedése a szükséges kapcsolatokhoz. Ha például ugyanazt a kódot futtatja, mint az előző példában, de egy szükséges kapcsolattal, a következő eredmény látható a hibakeresési nézetben a SaveChanges meghívása előtt:

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

A vártnak megfelelően a függők/gyermekek mostantól a következőként Deletedvannak megjelölve: . Figyelje meg azonban, hogy a törölt entitások navigációs beállításai nem változtak. Ez furcsának tűnhet, de elkerüli az entitások törölt gráfjának teljes aprítását azáltal, hogy minden navigációt kiürít. Vagyis a blog, az objektum és a bejegyzések még a törlés után is entitások grafikonját alkotják. Ez sokkal egyszerűbbé teszi az entitások gráfjának törlését, mint az EF6 esetében, ahol a gráfot aprították.

Kaszkádolt törlés időzítése és újraszkenvedése

Alapértelmezés szerint a kaszkádolt törlés azonnal megtörténik, amint a szülőelem vagy főelem meg van jelölve Deleted. Ez ugyanaz, mint az árvák törlésekor, ahogy azt korábban leírtuk. Az árvák törléséhez hasonlóan ez a folyamat is késleltethető, amíg a SaveChanges meghívása, vagy akár teljesen le nem tiltása nem történik meg a megfelelő beállítással ChangeTracker.CascadeDeleteTiming . Ez ugyanúgy hasznos, mint az árvák törlése, beleértve a gyermekek/eltartottak újracasolását a fő vagy szülő törlése után.

Kaszkád törlések, valamint az árvák törlése bármikor végrehajtható a ChangeTracker.CascadeChanges() hívással. Ha ezt kombinálja a kaszkádos törlés időzítésének Never beállításaival, az biztosítja, hogy a kaszkádos törlések soha ne történjenek meg, kivéve, ha az EF Core kifejezetten utasítást kap arra.

Jótanács

A kaszkádtörlés és az árvák törlése szorosan összefügg. Mindkettő a függő/gyermek entitások törlését eredményezi, ha az adott esetben szükséges főentitáshoz/szülőhöz való kapcsolat megszakad. Kaszkádolt törlés esetén ez a leválasztás azért történik, mert maga a fő/szülő van törölve. Árvák esetén a fő/szülő entitás továbbra is létezik, de már nem kapcsolódik a függő/gyermek entitásokhoz.

Többszörös kapcsolatok

A több-többhöz kapcsolatok az EF Core-ban egy összekötő entitás használatával valósulnak meg. A több-a-többhöz kapcsolat mindkét oldala egy-a-többhöz kapcsolattal kapcsolódik ehhez az összekapcsolt entitáshoz. Ez az illesztés entitás explicit módon definiálható és leképezhető, vagy implicit módon és rejtetten hozható létre. Mindkét esetben a mögöttes viselkedés ugyanaz. Először ezt a mögöttes viselkedést fogjuk megvizsgálni, hogy megértsük, hogyan működik a több-több közötti kapcsolatok nyomon követése.

A több-a-többhöz kapcsolatok működése

Fontolja meg ezt az EF Core-modellt, amely egy kifejezetten definiált összekapcsoló entitástípus használatával hoz létre több-a-többhöz kapcsolatot a bejegyzések és címkék között.

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
}

Figyelje meg, hogy az PostTag illesztés entitástípusa két idegenkulcs-tulajdonságot tartalmaz. Ebben a modellben ahhoz, hogy egy bejegyzés egy címkéhez kapcsolódjon, rendelkeznie kell egy PostTag illesztő entitással, ahol az PostTag.PostId idegen kulcs értéke megegyezik az Post.Id elsődleges kulcs értékével, és ahol az PostTag.TagId idegen kulcs értéke megegyezik az Tag.Id elsődleges kulcs értékével. Például:

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

A kód futtatása után a változáskövetési hibakeresési nézet azt mutatja, hogy a bejegyzés és a címke az új PostTag illesztés entitáshoz kapcsolódik:

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

Figyelje meg, hogy a gyűjtemény navigációi ki lettek javítva Post és Tag, ahogy a referencianavigációk PostTag is ki lettek javítva. Ezek a kapcsolatok az FK-értékek helyett navigációkkal módosíthatók, ugyanúgy, mint az előző példákban. A fenti kód például módosítható a kapcsolat hozzáadásához az illesztés entitás referencianavigációinak beállításával:

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

Ez pontosan ugyanazt a változást eredményezi az FK-kban és a navigációkban, mint az előző példában.

Navigációk kihagyása

Nehézkes lehet manuálisan módosítani az illesztési táblát. A több-a-többhöz kapcsolatok közvetlenül kezelhetők olyan speciális gyűjteménynavigációk használatával, amelyek "átugorják" az illesztési entitást. A fenti modellhez például két kihagyott navigáció adható hozzá; az egyik a Post-tól a Címkékig, a másik pedig a Címkéktől a Bejegyzésekig:

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
}

Ehhez a több-a-többhöz kapcsolathoz a következő konfiguráció szükséges ahhoz, hogy a kihagyott navigációk és a normál navigációk mind ugyanazt a több-a-többhöz kapcsolatot használják:

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

A több-a-többhöz való kapcsolatok leképezésével kapcsolatos további információkért lásd a Kapcsolatok című témakört.

A keresztülugorható navigációk úgy néznek ki és viselkednek, mint a normál gyűjteménynavigációk. Azonban az idegen kulcsértékek kezelési módja eltérő. Társítsunk egy bejegyzést egy címkéhez, de ezúttal a navigáció kihagyását használva:

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

Figyelje meg, hogy ez a kód nem használja az illesztés entitást. Ehelyett csak egy entitást ad hozzá egy navigációs gyűjteményhez ugyanúgy, mint ha ez egy-a-többhöz kapcsolat lenne. Az eredményül kapott hibakeresési nézet lényegében ugyanaz, mint korábban:

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

Figyelje meg, hogy az PostTag illesztés entitásának egy példánya automatikusan létre lett hozva, és az FK-értékek a címkének és a bejegyzésnek a most társított PK-értékeire vannak állítva. Az FK-értékeknek megfelelően az összes normál referencia- és gyűjteménynavigáció ki lett javítva. Mivel ez a modell kihagyott navigációkat is tartalmaz, ezeket is kijavítottuk. Pontosabban, annak ellenére, hogy hozzáadtuk a címkét a Post.Tags kihagyó navigációhoz, a Tag.Posts kapcsolat másik oldalán található inverz kihagyó navigáció is ki lett javítva, hogy tartalmazza a társított bejegyzést.

Érdemes megjegyezni, hogy a mögöttes több-a-többhöz kapcsolatok még akkor is közvetlenül kezelhetők, ha a kihagyott navigációk felül vannak rétegzve. Például a címke és a bejegyzés ugyanúgy társíthatók, mint ahogy a navigációk kihagyása előtt tettük.

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

Vagy FK-értékek használata:

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

Ez továbbra is azt eredményezi, hogy a kihagyott navigációk helyesen lesznek javítva, ami ugyanazt a hibakeresési nézet kimenetét eredményezi, mint az előző példában.

Csak a navigációs elemek kihagyása

Az előző szakaszban az átugró navigációkat adtuk hozzá a két mögöttes egy-a-többhöz kapcsolat teljes definiálása mellett. Ez hasznos annak szemléltetésére, hogy mi történik az FK-értékekkel, de gyakran szükségtelen. Ehelyett a több-a-többhöz kapcsolat kizárólag az átugró navigációk használatával határozható meg. A több-több közötti kapcsolat így van meghatározva a modellben a dokumentum tetején. Ezzel a modellel ismét társíthatunk egy bejegyzést és egy címkét úgy, hogy hozzáadunk egy bejegyzést a Tag.Posts kihagyó navigációs sávhoz (vagy másik lehetőségként hozzáadunk egy címkét a Post.Tags kihagyó navigációhoz):

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

A módosítás után a hibakeresési nézetből kiderül, hogy az EF Core létrehozott egy példányt az illesztés entitásának Dictionary<string, object> megjelenítéséhez. Ez az illesztés entitás olyan PostsIdTagsId külső kulcstulajdonságokat tartalmaz, amelyek a bejegyzés PK-értékeinek és a társított címkének megfelelően lettek beállítva.

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

Az implicit illesztés entitásairól és az entitástípusok használatáról Dictionary<string, object> további információt a Kapcsolatok című témakörben talál.

Fontos

Az entitástípusok konvenciók szerinti összekapcsolásához használt CLR-típus a jövőbeli kiadásokban változhat a teljesítmény javítása érdekében. Ne függj attól, hogy az illesztés típusa Dictionary<string, object>, hacsak nincs explicit módon konfigurálva.

Entitások összekapcsolása terhelésekkel

Eddig az összes példa egy kapcsoló entitástípust használt (legyen az explicit vagy implicit), amely csak a több-a-többhöz kapcsolathoz szükséges két idegenkulcs-tulajdonságot tartalmazza. Ezeknek az FK-értékeknek egyikét sem kell explicit módon beállítania az alkalmazásnak a kapcsolatok módosításakor, mivel az értékek a kapcsolódó entitások elsődleges kulcstulajdonságaiból származnak. Ez lehetővé teszi, hogy az EF Core hiányzó adatok nélkül hozza létre az illesztési entitás példányait.

Adatcsomagok generált értékekkel

Az EF Core támogatja a további tulajdonságok hozzáadását az illesztés entitástípusához. Ez a csatlakozási entitásnak "terhelés" adása. Például adjunk hozzá egy TaggedOn tulajdonságot a PostTag kapcsoló entitáshoz:

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
}

Az EF Core nem állítja be ezt a payload tulajdonságot, amikor létrehoz egy illesztési entitáspéldányt. Ennek kezelésére a leggyakoribb módszer az automatikusan generált értékekkel rendelkező hasznos adattulajdonságok használata. Például, a TaggedOn tulajdonság beállítható úgy, hogy minden egyes új entitás beszúrásakor tároló által generált időbélyeget használjon.

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

A bejegyzések mostantól ugyanúgy címkézhetők, mint korábban:

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

A SaveChanges hívása után a változáskövető hibakeresési nézet azt mutatja, hogy az adatcímke tulajdonság megfelelően lett beállítva.

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

Adatcsomagértékek explicit beállítása

Az előző példából következően adjunk hozzá egy olyan hasznos adattulajdonságot, amely nem használ automatikusan generált értéket:

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
}

A bejegyzések mostantól ugyanúgy címkézhetők, mint korábban, és az illesztés entitása továbbra is automatikusan létrejön. Ez az entitás ezután az Accessing Tracked Entities (Nyomon követett entitások elérése) című szakaszban ismertetett mechanizmusok egyikével érhető el. Az alábbi kód például az illesztés entitáspéldányának elérésére használja DbSet<TEntity>.Find.

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

Miután az illesztési entitást megtaláltuk, a szokásos módon módosítható – ebben a példában a TaggedBy előtt beállítjuk a hasznos teher tulajdonságot, majd meghívjuk a VáltozásokMentése parancsot.

Megjegyzés:

Vegye figyelembe, hogy itt egy hívásra van szükség a ChangeTracker.DetectChanges()-hoz, hogy az EF Core észlelje a navigációs tulajdonság változását, és létrehozza az illesztési entitáspéldányt, mielőtt a Find-t használná. További információ: Változásészlelés és értesítések .

Másik lehetőségként a kapcsoló entitást kifejezetten létrehozhatja, hogy egy bejegyzést címkével társítson. Például:

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

Végül a hasznos adatok beállításának másik módja az, hogy a SaveChanges felülbírálásával vagy az DbContext.SavingChanges esemény használatával az entitások feldolgozására kerül sor az adatbázis frissítése előtt. Például:

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