Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Každá instance DbContext sleduje změny provedené u entit. Tyto sledované entity následně řídí změny v databázi při volání SaveChanges.
Tento dokument představuje přehled sledování změn Entity Framework Core (EF Core) a jeho vztah k dotazům a aktualizacím.
Tip
Celý kód v tomto dokumentu můžete spustit a ladit tak, že si stáhnete ukázkový kód z GitHubu.
Jak sledovat entity
Instance entit se sledují, když jsou:
- Vrácené z dotazu spuštěného v databázi
- Explicitně připojené k DbContext pomocí
Add,Attach,Updatenebo podobné metody - Detekované jako nové entity propojené s existujícími sledovanými entitami
Instance entit se už nesledují, když:
- DbContext je odstraněn.
- Sledování změn je vymazáno.
- Entity jsou explicitně odpojené.
DbContext je navržený tak, aby představoval krátkodobou jednotku práce, jak je popsáno v tématu o inicializaci a konfiguraci DbContext. To znamená, že rozložení DbContext je normální způsob, zastavit sledování entit. Jinými slovy, doba života DbContext by měla být:
- Vytvoření instance DbContext
- Sledování některých entit
- Provedení některých změn entit
- Volání SaveChanges pro aktualizaci databáze
- Uvolnění instance DbContext
Tip
Při tomto přístupu není nutné vymazat sledování změn ani explicitně odpojovat instance entit. Pokud však potřebujete oddělit entity, je volání ChangeTracker.Clear efektivnější než oddělování entit po jedné.
Stavy entit
Každá entita je přidružená k danému EntityState:
-
Detachednesleduje entity DbContext. - Entity
Addedjsou nové a ještě nebyly vloženy do databáze. To znamená, že budou vloženy při volání SaveChanges. - Entity
Unchangedse nezměnily od doby, kdy byly dotazovány z databáze. Všechny entity vrácené z dotazů jsou zpočátku v tomto stavu. - Entity
Modifiedse změnily od doby, kdy byly dotazovány z databáze. To znamená, že se po volání SaveChanges aktualizují. - Entity
Deletedexistují v databázi, ale jsou označeny k odstranění při volání SaveChanges.
EF Core sleduje změny na úrovni vlastností. Pokud je například změněna pouze jedna hodnota vlastnosti, aktualizace databáze změní pouze tuto hodnotu. Vlastnosti však mohou být označeny jako změněné pouze tehdy, když je samotná entita ve stavu Modified. (Nebo – z jiného pohledu – stav Modified znamená, že alespoň jedna hodnota vlastnosti byla označena jako změněná.)
Následující tabulka shrnuje jednotlivé stavy:
| Stav entity | Sledováno pomocí DbContext | Existuje v databázi | Změněné vlastnosti | Akce při SaveChanges |
|---|---|---|---|---|
Detached |
No | - | - | - |
Added |
Yes | No | - | Insert |
Unchanged |
Yes | Yes | No | - |
Modified |
Yes | Yes | Yes | Update |
Deleted |
Yes | Yes | - | Delete |
Note
V tomto textu jsou pro přehlednost použity termíny relační databáze. Databáze NoSQL obvykle podporují podobné operace, ale případně s jinými názvy. Další informace najdete v dokumentaci poskytovatele databáze.
Sledování z dotazů
Sledování změn EF Core funguje nejlépe, když se stejná instance DbContext používá jak k dotazování na entity, tak k jejich aktualizaci voláním SaveChanges. Je to proto, že EF Core automaticky sleduje stav dotazovaných entit a poté zjišťuje všechny změny provedené v těchto entitách při volání SaveChanges.
Tento přístup má několik výhod oproti explicitnímu sledování instancí entit:
- Je jednoduchý. Stavy entit pouze zřídka potřebují být manipulovány explicitně – EF Core se postará o změny stavu.
- Aktualizace jsou omezené pouze na hodnoty, které se skutečně změnily.
- Hodnoty stínových vlastností se zachovají a použijí podle potřeby. To platí zejména v případě, že jsou cizí klíče uložené ve stínovém stavu.
- Původní hodnoty vlastností se zachovají automaticky a používají se k efektivním aktualizacím.
Jednoduchý dotaz a aktualizace
Představte si například jednoduchý model blogů/příspěvků:
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; }
}
Tento model můžeme použít k vyhledávání blogů a příspěvků a následně provést některé aktualizace databáze:
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();
Volání SaveChanges vede k následujícím aktualizacím databáze pomocí SQLite jako ukázkové databáze:
-- 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();
Zobrazení ladění sledování změn je skvělý způsob, jak vizualizovat, které entity se sledují a jaké jsou jejich stavy. Například vložením následujícího kódu do výše uvedené ukázky před voláním SaveChanges:
context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
Generuje následující výstup:
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}
Konkrétně si všimněte:
- Vlastnost
Blog.Nameje označena jako změněná (Name: '.NET Blog (Updated!)' Modified Originally '.NET Blog') a výsledkem je blog ve stavuModified. - Vlastnost
Post.Titlepříspěvku 2 je označena jako změněná (Title: 'Announcing F# 5.0' Modified Originally 'Announcing F# 5') a výsledkem je tento příspěvek ve stavuModified. - Ostatní hodnoty vlastností příspěvku 2 se nezměnily, a proto nejsou označené jako změněné. Proto nejsou tyto hodnoty zahrnuty do aktualizace databáze.
- Druhý příspěvek nebyl nijak změněn. Proto je stále ve stavu
Unchangeda není zahrnut do aktualizace databáze.
Dotaz, pak vložení, aktualizace a odstranění
Aktualizace jako v předchozím příkladu je možné kombinovat s vloženími a odstraněními ve stejné jednotce práce. Například:
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();
V tomto příkladu:
- Blog a související příspěvky se dotazují z databáze a sledují.
- Vlastnost
Blog.Nameje změněna. - Do kolekce existujících příspěvků pro blog se přidá nový příspěvek.
- Existující příspěvek je označen k odstranění voláním DbContext.Remove
Když se znovu podíváme na zobrazení ladění modulu sledování změn před voláním SaveChanges, je vidět, jak EF Core tyto změny sleduje:
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}
Všimněte si, že:
- Blog je označen jako
Modified. Tím se vygeneruje aktualizace databáze. - Příspěvek 2 je označen jako
Deleted. Tím se vygeneruje odstranění databáze. - Nový příspěvek s dočasným ID je přidružený k blogu 1 a je označen jako
Added. Tím se vygeneruje vložení do databáze.
Výsledkem jsou následující databázové příkazy (pomocí SQLite), když se volá SaveChanges:
-- 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();
Další informace o vkládání a odstraňování entit najdete v tématu o explicitním sledování entit . Další informace o tom, jak EF Core automaticky rozpozná změny, jako je tato, najdete v tématu o detekci změn a oznámeních .
Tip
Voláním ChangeTracker.HasChanges() zjistěte, jestli byly provedeny nějaké změny, které způsobí, že funkce SaveChanges provede aktualizace databáze. Pokud HasChanges vrátí hodnotu false, SaveChanges bude no-op.