Megosztás:


Változáskövetés az EF Core-ban

Minden DbContext példány nyomon követi az entitások módosításait. Ezek a nyomon követett entitások viszont az adatbázis változásait hajtják végre, amikor SaveChanges meghívják őket.

Ez a dokumentum áttekintést nyújt az Entity Framework Core (EF Core) változáskövetéséről, valamint arról, hogyan kapcsolódik a lekérdezésekhez és frissítésekhez.

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.

Entitások nyomon követése

Az entitáspéldányok a következő esetekben lesznek nyomon követve:

  • Az adatbázison végrehajtott lekérdezésből visszaadva
  • Explicit módon csatolva a DbContexthez Add, Attach, Updatevagy hasonló módszerekkel
  • Új entitásként észlelve, amely a meglévő nyomon követett entitásokhoz csatlakozik

Az entitáspéldányok a továbbiakban nem lesznek nyomon követve, ha:

  • A DbContext felszabadításra került
  • A változáskövető törlődik
  • Az entitások explicit módon vannak leválasztva

A DbContext úgy lett kialakítva, hogy egy rövid életű munkaegységet képviseljen a DbContext inicializálásában és konfigurálásában leírtak szerint. Ez azt jelenti, hogy az entitások nyomon követésének leállításához a DbContext letiltása a szokásos módszer . Más szóval a DbContext élettartamának a következőnek kell lennie:

  1. A DbContext-példány létrehozása
  2. Egyes entitások nyomon követése
  3. Az entitások néhány módosítása
  4. A SaveChanges hívása az adatbázis frissítéséhez
  5. A DbContext-példány elvetése

Jótanács

Ennek a megközelítésnek a használatakor nem szükséges törölni a változáskövetőt, vagy explicit módon leválasztani az entitáspéldányokat. Ha azonban le kell választania az entitásokat, akkor a hívás ChangeTracker.Clear hatékonyabb, mint az entitások egyenkénti leválasztása.

Entitásállapotok

Minden entitás egy adott EntityState:

  • Detachedentitásokat a rendszer nem követi nyomon.DbContext
  • Added az entitások újak, és még nem lettek beszúrva az adatbázisba. Ez azt jelenti, hogy a rendszer beszúrja őket, amikor SaveChanges meghívják őket.
  • Unchanged az entitások nem módosultak, mióta lekérdezték őket az adatbázisból. A lekérdezésekből visszaadott összes entitás kezdetben ebben az állapotban van.
  • Modified entitások módosultak, mióta lekérdezték őket az adatbázisból. Ez azt jelenti, hogy a SaveChanges meghívásakor frissülnek.
  • Deleted entitások léteznek az adatbázisban, de a Rendszer a SaveChanges meghívásakor törli őket.

Az EF Core a tulajdonságok szintjén követi nyomon a változásokat. Ha például csak egyetlen tulajdonságértéket módosít, akkor az adatbázis frissítése csak ezt az értéket módosítja. A tulajdonságok azonban csak akkor jelölhetők módosítottként, ha maga az entitás módosított állapotban van. (Másik szempontból a Módosított állapot azt jelenti, hogy legalább egy tulajdonságérték módosult.)

Az alábbi táblázat a különböző állapotokat foglalja össze:

Entitás állapota A DbContext által követve Létezik az adatbázisban Módosított tulajdonságok Művelet a változások mentésén
Detached Nem - - -
Added Igen Nem - Beilleszt
Unchanged Igen Igen Nem -
Modified Igen Igen Igen Frissítés
Deleted Igen Igen - Törlés

Megjegyzés:

Ez a szöveg a relációs adatbázis kifejezéseket használja az egyértelműség érdekében. A NoSQL-adatbázisok általában támogatják a hasonló műveleteket, de valószínűleg más néven. További információért tekintse meg az adatbázis-szolgáltató dokumentációját.

Lekérdezésekből származó nyomkövetés

Az EF Core változáskövetése akkor működik a legjobban, ha ugyanazt DbContext a példányt használják az entitások lekérdezésére, és hívással SaveChangesfrissítik őket. Ennek az az oka, hogy az EF Core automatikusan nyomon követi a lekérdezett entitások állapotát, majd észleli az entitások módosításait a SaveChanges meghívásakor.

Ez a megközelítés számos előnnyel rendelkezik az entitáspéldányok explicit nyomon követésével szemben:

  • Ez egyszerű. Az entitásállapotokat ritkán kell explicit módon manipulálni – az EF Core gondoskodik az állapotváltozásról.
  • A frissítések csak azokra az értékekre korlátozódnak, amelyek ténylegesen megváltoztak.
  • Az árnyéktulajdonságok értékei szükség szerint megmaradnak és használhatók. Ez különösen akkor fontos, ha az idegen kulcsok árnyékállapotban vannak tárolva.
  • A tulajdonságok eredeti értékei automatikusan megmaradnak, és a hatékony frissítésekhez használhatók.

Egyszerű lekérdezés és frissítés

Vegyük például egy egyszerű blog-/bejegyzésmodellt:

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }

    public IList<Post> Posts { get; } = new List<Post>();
}

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

Ezzel a modellel lekérdezhetjük a blogokat és a bejegyzéseket, majd frissíthetjük az adatbázist:

using var context = new BlogsContext();

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

blog.Name = ".NET Blog (Updated!)";

await foreach (var post in blog.Posts.AsQueryable().Where(e => !e.Title.Contains("5.0")).AsAsyncEnumerable())
{
    post.Title = post.Title.Replace("5", "5.0");
}

await context.SaveChangesAsync();

A SaveChanges hívása az alábbi adatbázis-frissítéseket eredményezi, példaadatbázisként használva az SQLite-t:

-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p0='.NET Blog (Updated!)' (Size = 20)], CommandType='Text', CommandTimeout='30']
UPDATE "Blogs" SET "Name" = @p0
WHERE "Id" = @p1;
SELECT changes();

-- Executed DbCommand (0ms) [Parameters=[@p1='2' (DbType = String), @p0='Announcing F# 5.0' (Size = 17)], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "Title" = @p0
WHERE "Id" = @p1;
SELECT changes();

A változáskövető hibakeresési nézet nagyszerű módja annak, hogy megjelenítse a nyomon követett entitásokat és azok állapotát. Például szúrja be a következő kódot a fenti mintába a SaveChanges meghívása előtt:

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

A következő kimenetet hozza létre:

Blog {Id: 1} Modified
  Id: 1 PK
  Name: '.NET Blog (Updated!)' Modified Originally '.NET Blog'
  Posts: [{Id: 1}, {Id: 2}, {Id: 3}]
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}
Post {Id: 2} Modified
  Id: 2 PK
  BlogId: 1 FK
  Content: 'F# 5 is the latest version of F#, the functional programming...'
  Title: 'Announcing F# 5.0' Modified Originally 'Announcing F# 5'
  Blog: {Id: 1}

Figyelje meg a következőt:

  • A Blog.Name tulajdonság módosultként (Name: '.NET Blog (Updated!)' Modified Originally '.NET Blog') van megjelölve, és ez azt eredményezi, hogy a blog állapotban Modified van.
  • A második bejegyzés Post.Title tulajdonsága módosítottként (Title: 'Announcing F# 5.0' Modified Originally 'Announcing F# 5') van megjelölve, és ez azt eredményezi, hogy ez a bejegyzés a Modified állapotban van.
  • A 2. utáni többi tulajdonságérték nem módosult, ezért nem módosultként van megjelölve. Ezért ezek az értékek nem szerepelnek az adatbázis frissítésében.
  • A másik bejegyzés semmilyen módon nem módosult. Ezért van még mindig állapotban Unchanged , és nem szerepel az adatbázis frissítésében.

Lekérdezés, majd beszúrás, frissítés és törlés

Az előző példához hasonló frissítések kombinálhatók beszúrásokkal és törlésekkel ugyanabban a munkaegységben. Például:

using var context = new BlogsContext();

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

// Modify property values
blog.Name = ".NET Blog (Updated!)";

// Insert a new Post
blog.Posts.Add(
    new Post
    {
        Title = "What’s next for System.Text.Json?", Content = ".NET 5.0 was released recently and has come with many..."
    });

// Mark an existing Post as Deleted
var postToDelete = blog.Posts.Single(e => e.Title == "Announcing F# 5");
context.Remove(postToDelete);

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

await context.SaveChangesAsync();

Ebben a példában:

  • A blogot és a kapcsolódó bejegyzéseket lekérdezik az adatbázisból, és nyomon követik
  • A Blog.Name tulajdonság megváltozott
  • Új bejegyzést adunk hozzá a blog meglévő bejegyzéseinek gyűjteményéhez
  • A rendszer meghív egy meglévő bejegyzést törlésre DbContext.Remove

A SaveChanges hívása előtt újra megtekinti a változáskövető hibakeresési nézetet , és megmutatja, hogy az EF Core hogyan követi nyomon ezeket a módosításokat:

Blog {Id: 1} Modified
  Id: 1 PK
  Name: '.NET Blog (Updated!)' Modified Originally '.NET Blog'
  Posts: [{Id: 1}, {Id: 2}, {Id: 3}, {Id: -2147482638}]
Post {Id: -2147482638} Added
  Id: -2147482638 PK Temporary
  BlogId: 1 FK
  Content: '.NET 5.0 was released recently and has come with many...'
  Title: 'What's next for System.Text.Json?'
  Blog: {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}
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: {Id: 1}

Figyelje meg az alábbiakat:

  • A blog a következőként Modifiedvan megjelölve: . Ez létrehoz egy adatbázis-frissítést.
  • A 2. bejegyzés a következőképpen van megjelölve Deleted: . Ez létrehoz egy adatbázis-törlést.
  • Az ideiglenes azonosítóval rendelkező új bejegyzés az 1. bloghoz van társítva, és a következőképpen van megjelölve Added: . Ez létrehoz egy adatbázis-beszúrást.

Ez a következő adatbázis-parancsokat eredményezi (SQLite használatával) a SaveChanges meghívásakor:

-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p0='.NET Blog (Updated!)' (Size = 20)], CommandType='Text', CommandTimeout='30']
UPDATE "Blogs" SET "Name" = @p0
WHERE "Id" = @p1;
SELECT changes();

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

-- Executed DbCommand (0ms) [Parameters=[@p0='1' (DbType = String), @p1='.NET 5.0 was released recently and has come with many...' (Size = 56), @p2='What's next for System.Text.Json?' (Size = 33)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Posts" ("BlogId", "Content", "Title")
VALUES (@p0, @p1, @p2);
SELECT "Id"
FROM "Posts"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();

Az entitások beszúrásával és törlésével kapcsolatos további információkért lásd az Explicitly Tracking Entities című témakört. Lásd a változásészlelés és az értesítések részben további információt arról, hogyan észleli automatikusan az EF Core az ilyen jellegű változásokat.

Jótanács

Hívás ChangeTracker.HasChanges() annak megállapítására, hogy történtek-e olyan módosítások, amelyek miatt a SaveChanges frissíti az adatbázist. Ha a HasChanges hamis értéket ad vissza, akkor a SaveChanges no-oplesz.