Explicitní sledování entit
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.
Sledování změn Entity Framework Core (EF Core) funguje nejlépe, když se stejná DbContext instance používá k dotazování na entity a 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 je popsaný ve službě Change Tracking v EF Core.
Tip
Tento dokument předpokládá, že stavy entit a základy sledování změn EF Core jsou srozumitelné. Další informace o těchto tématech najdete v tématu Sledování změn v EF Core .
Tip
Celý kód v tomto dokumentu můžete spustit a ladit tak, že si stáhnete ukázkový kód z GitHubu.
Tip
Pro zjednodušení se v tomto dokumentu používají a odkazují synchronní metody, jako jsou SaveChanges, a nikoli jejich asynchronní ekvivalenty, jako jsou SaveChangesAsync. Volání a čekání na asynchronní metodu lze nahradit, pokud není uvedeno jinak.
Úvod
Entity mohou být explicitně "připojeny" k takové DbContext , že kontext pak tyto entity sleduje. To je primárně užitečné v těchto případech:
- Vytváření nových entit, které budou vloženy do databáze.
- Opětovné připojení odpojených entit, které byly dříve dotazovány jinou instancí DbContext.
První z nich bude potřebovat většina aplikací a je primárně zpracována metodami DbContext.Add .
Sekundu potřebují jenom aplikace, které mění entity nebo jejich vztahy, zatímco entity se nesledují. Webová aplikace může například odesílat entity webovému klientovi, kde uživatel provádí změny a odesílá entity zpět. Tyto entity se označují jako "odpojené", protože byly původně dotazovány z DbContext, ale při odeslání klientovi byly od tohoto kontextu odpojeny.
Webová aplikace teď musí tyto entity znovu připojit, aby byly znovu sledovány a indikovaly změny, které byly provedeny tak, aby SaveChanges mohly provádět příslušné aktualizace databáze. To je primárně zpracováváno metodami DbContext.Attach a DbContext.Update metodami.
Tip
Připojení entit ke stejné instanci DbContext, ze které byly dotazovány, by nemělo být normálně potřeba. Neprovádějte rutinně dotaz bez sledování a pak připojte vrácené entity ke stejnému kontextu. To bude pomalejší než použití sledovacího dotazu a může také vést k problémům, jako jsou chybějící hodnoty stínových vlastností, což znesnadňuje správné získání.
Vygenerované a explicitní hodnoty klíče
Ve výchozím nastavení jsou celočíselné a guid vlastnosti klíče nakonfigurované tak, aby používaly automaticky generované hodnoty klíče. To má velkou výhodu pro sledování změn: hodnota klíče bez nastavení označuje, že entita je "nová". "Nový" znamená, že ještě nebyl vložen do databáze.
V následujících částech se používají dva modely. První je nakonfigurovaná tak, aby nepoužíla vygenerované hodnoty klíče:
public class Blog
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Name { get; set; }
public IList<Post> Posts { get; } = new List<Post>();
}
public class Post
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
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; }
}
Negenerované hodnoty klíče (tj. explicitně nastavené) se v každém příkladu zobrazují jako první, protože vše je velmi explicitní a snadno se postupuje. Potom následuje příklad, ve kterém se používají vygenerované hodnoty klíče:
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; }
}
Všimněte si, že klíčové vlastnosti v tomto modelu nepotřebují žádnou další konfiguraci, protože použití vygenerovaných hodnot klíčů je výchozí pro jednoduché celočíselné klíče.
Vkládání nových entit
Explicitní hodnoty klíče
Entita musí být sledována Added
ve stavu, který SaveChangesmá být vložen . Entity jsou obvykle vloženy do přidaného stavu voláním jednoho z DbContext.Add, DbContext.AddRange, DbContext.AddAsyncDbContext.AddRangeAsyncnebo ekvivalentní metody v DbSet<TEntity>.
Tip
Všechny tyto metody fungují stejným způsobem v kontextu sledování změn. Další informace najdete v tématu Další funkce sledování změn.
Pokud chcete například začít sledovat nový blog:
context.Add(
new Blog { Id = 1, Name = ".NET Blog", });
Kontrola zobrazení ladění sledování změn po tomto volání ukazuje, že kontext sleduje novou entitu Added
ve stavu:
Blog {Id: 1} Added
Id: 1 PK
Name: '.NET Blog'
Posts: []
Metody Add ale nepracují jenom na jednotlivé entitě. Ve skutečnosti začnou sledovat celý graf souvisejících entit a umístí je do Added
stavu. Pokud například chcete vložit nový blog a přidružené nové příspěvky:
context.Add(
new Blog
{
Id = 1,
Name = ".NET Blog",
Posts =
{
new Post
{
Id = 1,
Title = "Announcing the Release of EF Core 5.0",
Content = "Announcing the release of EF Core 5.0, a full featured cross-platform..."
},
new Post
{
Id = 2,
Title = "Announcing F# 5",
Content = "F# 5 is the latest version of F#, the functional programming language..."
}
}
});
Kontext teď sleduje všechny tyto entity takto Added
:
Blog {Id: 1} Added
Id: 1 PK
Name: '.NET Blog'
Posts: [{Id: 1}, {Id: 2}]
Post {Id: 1} Added
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} Added
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 explicitní hodnoty byly nastaveny pro Id
klíčové vlastnosti v příkladech výše. Důvodem je to, že zde vytvořený model je nakonfigurovaný tak, aby používal explicitně nastavené hodnoty klíče, a ne automaticky vygenerované hodnoty klíče. Pokud nepoužíváte vygenerované klíče, musí být vlastnosti klíče explicitně nastaveny před voláním Add
. Tyto hodnoty klíče se pak vloží při zavolání SaveChanges. Například při použití SQLite:
-- Executed DbCommand (0ms) [Parameters=[@p0='1' (DbType = String), @p1='.NET Blog' (Size = 9)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Blogs" ("Id", "Name")
VALUES (@p0, @p1);
-- Executed DbCommand (0ms) [Parameters=[@p2='1' (DbType = String), @p3='1' (DbType = String), @p4='Announcing the release of EF Core 5.0, a full featured cross-platform...' (Size = 72), @p5='Announcing the Release of EF Core 5.0' (Size = 37)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Posts" ("Id", "BlogId", "Content", "Title")
VALUES (@p2, @p3, @p4, @p5);
-- Executed DbCommand (0ms) [Parameters=[@p0='2' (DbType = String), @p1='1' (DbType = String), @p2='F# 5 is the latest version of F#, the functional programming language...' (Size = 72), @p3='Announcing F# 5' (Size = 15)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Posts" ("Id", "BlogId", "Content", "Title")
VALUES (@p0, @p1, @p2, @p3);
Všechny tyto entity jsou po dokončení SaveChanges sledovány ve Unchanged
stavu, protože tyto entity nyní existují v databázi:
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
Posts: [{Id: 1}, {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}
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}
Vygenerované hodnoty klíče
Jak je uvedeno výše, celočíselné a GUID vlastnosti klíče jsou nakonfigurované tak, aby ve výchozím nastavení používaly automaticky generované hodnoty klíče. To znamená, že aplikace nesmí explicitně nastavit žádnou hodnotu klíče. Pokud chcete například vložit nový blog a publikuje všechny s vygenerovanými hodnotami klíče:
context.Add(
new Blog
{
Name = ".NET Blog",
Posts =
{
new Post
{
Title = "Announcing the Release of EF Core 5.0",
Content = "Announcing the release of EF Core 5.0, a full featured cross-platform..."
},
new Post
{
Title = "Announcing F# 5",
Content = "F# 5 is the latest version of F#, the functional programming language..."
}
}
});
Stejně jako u explicitních hodnot klíčů teď kontext sleduje všechny tyto entity jako Added
:
Blog {Id: -2147482644} Added
Id: -2147482644 PK Temporary
Name: '.NET Blog'
Posts: [{Id: -2147482637}, {Id: -2147482636}]
Post {Id: -2147482637} Added
Id: -2147482637 PK Temporary
BlogId: -2147482644 FK Temporary
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: {Id: -2147482644}
Post {Id: -2147482636} Added
Id: -2147482636 PK Temporary
BlogId: -2147482644 FK Temporary
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5'
Blog: {Id: -2147482644}
Všimněte si v tomto případě, že se pro každou entitu vygenerovaly dočasné hodnoty klíče. Tyto hodnoty používá EF Core, dokud se nevolá SaveChanges, a v tomto okamžiku se skutečné hodnoty klíče načtou z databáze. Například při použití SQLite:
-- Executed DbCommand (0ms) [Parameters=[@p0='.NET Blog' (Size = 9)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Blogs" ("Name")
VALUES (@p0);
SELECT "Id"
FROM "Blogs"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();
-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p2='Announcing the release of EF Core 5.0, a full featured cross-platform...' (Size = 72), @p3='Announcing the Release of EF Core 5.0' (Size = 37)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Posts" ("BlogId", "Content", "Title")
VALUES (@p1, @p2, @p3);
SELECT "Id"
FROM "Posts"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();
-- Executed DbCommand (0ms) [Parameters=[@p0='1' (DbType = String), @p1='F# 5 is the latest version of F#, the functional programming language...' (Size = 72), @p2='Announcing F# 5' (Size = 15)], 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();
Po dokončení funkce SaveChanges byly všechny entity aktualizovány skutečnými hodnotami klíče a jsou sledovány ve Unchanged
stavu, protože teď odpovídají stavu v databázi:
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
Posts: [{Id: 1}, {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}
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}
Jedná se o úplně stejný koncový stav jako v předchozím příkladu, který používal explicitní hodnoty klíče.
Tip
Explicitní hodnota klíče je stále možné nastavit i při použití vygenerovaných hodnot klíče. EF Core se pak pokusí vložit pomocí této hodnoty klíče. Některé konfigurace databází, včetně SQL Serveru se sloupci Identity, nepodporují takové vložení a vyvolá se (alternativní řešení najdete v těchto dokumentech).
Připojení existujících entit
Explicitní hodnoty klíče
Entity vrácené z dotazů se sledují ve Unchanged
stavu. Stav Unchanged
znamená, že entita nebyla změněna od doby, kdy byla dotazována. Odpojenou entitu, například vrácenou z webového klienta v požadavku HTTP, může být do tohoto stavu vložena pomocí DbContext.Attach, DbContext.AttachRangenebo ekvivalentních metod v DbSet<TEntity>. Pokud chcete například začít sledovat existující blog:
context.Attach(
new Blog { Id = 1, Name = ".NET Blog", });
Poznámka
Zde uvedené příklady vytvářejí entity explicitně kvůli new
jednoduchosti. Za normálních okolností budou instance entit pocházet z jiného zdroje, jako je deserializace z klienta nebo vytváření z dat v HTTP Post.
Kontrola zobrazení ladění sledování změn po tomto volání ukazuje, že entita je sledována Unchanged
ve stavu:
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
Posts: []
Stejně jako Add
ve Attach
skutečnosti nastaví celý graf propojených entit do Unchanged
stavu. Pokud chcete například připojit existující blog a přidružené existující příspěvky:
context.Attach(
new Blog
{
Id = 1,
Name = ".NET Blog",
Posts =
{
new Post
{
Id = 1,
Title = "Announcing the Release of EF Core 5.0",
Content = "Announcing the release of EF Core 5.0, a full featured cross-platform..."
},
new Post
{
Id = 2,
Title = "Announcing F# 5",
Content = "F# 5 is the latest version of F#, the functional programming language..."
}
}
});
Kontext teď sleduje všechny tyto entity takto Unchanged
:
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
Posts: [{Id: 1}, {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}
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}
Volání SaveChanges v tomto okamžiku nebude mít žádný vliv. Všechny entity jsou označené jako Unchanged
, takže v databázi není nic aktualizovat.
Vygenerované hodnoty klíče
Jak je uvedeno výše, celočíselné a GUID vlastnosti klíče jsou nakonfigurované tak, aby ve výchozím nastavení používaly automaticky generované hodnoty klíče. To má velkou výhodu při práci s odpojenými entitami: hodnota klíče bez sady označuje, že entita ještě nebyla vložena do databáze. To umožňuje sledování změn automaticky rozpoznat nové entity a umístit je do Added
stavu. Zvažte například připojení tohoto grafu blogu a příspěvků:
context.Attach(
new Blog
{
Id = 1,
Name = ".NET Blog",
Posts =
{
new Post
{
Id = 1,
Title = "Announcing the Release of EF Core 5.0",
Content = "Announcing the release of EF Core 5.0, a full featured cross-platform..."
},
new Post
{
Id = 2,
Title = "Announcing F# 5",
Content = "F# 5 is the latest version of F#, the functional programming language..."
},
new Post
{
Title = "Announcing .NET 5.0",
Content = ".NET 5.0 includes many enhancements, including single file applications, more..."
},
}
});
Blog má hodnotu klíče 1, což znamená, že již v databázi existuje. Dva příspěvky mají také nastavené klíčové hodnoty, ale třetí není. EF Core uvidí tuto hodnotu klíče jako 0, což je výchozí hodnota CLR pro celé číslo. Výsledkem je označení nové entity ef Core jako Added
místo Unchanged
:
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
Posts: [{Id: 1}, {Id: 2}, {Id: -2147482636}]
Post {Id: -2147482636} Added
Id: -2147482636 PK Temporary
BlogId: 1 FK
Content: '.NET 5.0 includes many enhancements, including single file a...'
Title: 'Announcing .NET 5.0'
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} Unchanged
Id: 2 PK
BlogId: 1 FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Volání SaveChanges v tomto okamžiku Unchanged
nedělá nic s entitami, ale vloží novou entitu do databáze. Například při použití SQLite:
-- Executed DbCommand (0ms) [Parameters=[@p0='1' (DbType = String), @p1='.NET 5.0 includes many enhancements, including single file applications, more...' (Size = 80), @p2='Announcing .NET 5.0' (Size = 19)], 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();
Tady je důležité si uvědomit, že ef Core s vygenerovanými hodnotami klíčů dokáže automaticky odlišit nové od existujících entit v odpojeném grafu. Když použijete vygenerované klíče, EF Core při použití vygenerovaných klíčů vždy vloží entitu, pokud tato entita nemá nastavenou žádnou hodnotu klíče.
Aktualizace existujících entit
Explicitní hodnoty klíče
DbContext.Update, DbContext.UpdateRangea ekvivalentní metody DbSet<TEntity> se chovají přesně tak, jak Attach
jsou popsány výše uvedené metody, s tím rozdílem Modified
Unchanged
, že entity jsou vloženy do stavu. Pokud chcete například začít sledovat existující blog jako Modified
:
context.Update(
new Blog { Id = 1, Name = ".NET Blog", });
Kontrola zobrazení ladění sledování změn po tomto volání ukazuje, že kontext sleduje tuto entitu Modified
ve stavu:
Blog {Id: 1} Modified
Id: 1 PK
Name: '.NET Blog' Modified
Posts: []
Stejně jako u Add
a Attach
ve Update
skutečnosti označuje celý graf souvisejících entit jako Modified
. Pokud chcete například připojit existující blog a přidružené existující příspěvky jako Modified
:
context.Update(
new Blog
{
Id = 1,
Name = ".NET Blog",
Posts =
{
new Post
{
Id = 1,
Title = "Announcing the Release of EF Core 5.0",
Content = "Announcing the release of EF Core 5.0, a full featured cross-platform..."
},
new Post
{
Id = 2,
Title = "Announcing F# 5",
Content = "F# 5 is the latest version of F#, the functional programming language..."
}
}
});
Kontext teď sleduje všechny tyto entity takto Modified
:
Blog {Id: 1} Modified
Id: 1 PK
Name: '.NET Blog' Modified
Posts: [{Id: 1}, {Id: 2}]
Post {Id: 1} Modified
Id: 1 PK
BlogId: 1 FK Modified Originally <null>
Content: 'Announcing the release of EF Core 5.0, a full featured cross...' Modified
Title: 'Announcing the Release of EF Core 5.0' Modified
Blog: {Id: 1}
Post {Id: 2} Modified
Id: 2 PK
BlogId: 1 FK Modified Originally <null>
Content: 'F# 5 is the latest version of F#, the functional programming...' Modified
Title: 'Announcing F# 5' Modified
Blog: {Id: 1}
Volání SaveChanges v tomto okamžiku způsobí odeslání aktualizací do databáze pro všechny tyto entity. Například při použití SQLite:
-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p0='.NET Blog' (Size = 9)], CommandType='Text', CommandTimeout='30']
UPDATE "Blogs" SET "Name" = @p0
WHERE "Id" = @p1;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p3='1' (DbType = String), @p0='1' (DbType = String), @p1='Announcing the release of EF Core 5.0, a full featured cross-platform...' (Size = 72), @p2='Announcing the Release of EF Core 5.0' (Size = 37)], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "BlogId" = @p0, "Content" = @p1, "Title" = @p2
WHERE "Id" = @p3;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p3='2' (DbType = String), @p0='1' (DbType = String), @p1='F# 5 is the latest version of F#, the functional programming language...' (Size = 72), @p2='Announcing F# 5' (Size = 15)], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "BlogId" = @p0, "Content" = @p1, "Title" = @p2
WHERE "Id" = @p3;
SELECT changes();
Vygenerované hodnoty klíče
Stejně jako v případě Attach
, vygenerované hodnoty klíče mají stejnou hlavní výhodu pro Update
: hodnota klíče bez sady označuje, že entita je nová a ještě nebyla vložena do databáze. Stejně jako v případě Attach
, to umožňuje DbContext automaticky rozpoznat nové entity a dát je do Added
stavu. Zvažte například volání Update
pomocí tohoto grafu blogu a příspěvků:
context.Update(
new Blog
{
Id = 1,
Name = ".NET Blog",
Posts =
{
new Post
{
Id = 1,
Title = "Announcing the Release of EF Core 5.0",
Content = "Announcing the release of EF Core 5.0, a full featured cross-platform..."
},
new Post
{
Id = 2,
Title = "Announcing F# 5",
Content = "F# 5 is the latest version of F#, the functional programming language..."
},
new Post
{
Title = "Announcing .NET 5.0",
Content = ".NET 5.0 includes many enhancements, including single file applications, more..."
},
}
});
Stejně jako v příkladu Attach
se příspěvek bez hodnoty klíče zjistí jako nový a nastaví se na Added
stav. Ostatní entity jsou označené jako Modified
:
Blog {Id: 1} Modified
Id: 1 PK
Name: '.NET Blog' Modified
Posts: [{Id: 1}, {Id: 2}, {Id: -2147482633}]
Post {Id: -2147482633} Added
Id: -2147482633 PK Temporary
BlogId: 1 FK
Content: '.NET 5.0 includes many enhancements, including single file a...'
Title: 'Announcing .NET 5.0'
Blog: {Id: 1}
Post {Id: 1} Modified
Id: 1 PK
BlogId: 1 FK Modified Originally <null>
Content: 'Announcing the release of EF Core 5.0, a full featured cross...' Modified
Title: 'Announcing the Release of EF Core 5.0' Modified
Blog: {Id: 1}
Post {Id: 2} Modified
Id: 2 PK
BlogId: 1 FK Modified Originally <null>
Content: 'F# 5 is the latest version of F#, the functional programming...' Modified
Title: 'Announcing F# 5' Modified
Blog: {Id: 1}
Volání SaveChanges
v tomto okamžiku způsobí, že se aktualizace odešlou do databáze pro všechny existující entity a vloží se nová entita. Například při použití SQLite:
-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p0='.NET Blog' (Size = 9)], CommandType='Text', CommandTimeout='30']
UPDATE "Blogs" SET "Name" = @p0
WHERE "Id" = @p1;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p3='1' (DbType = String), @p0='1' (DbType = String), @p1='Announcing the release of EF Core 5.0, a full featured cross-platform...' (Size = 72), @p2='Announcing the Release of EF Core 5.0' (Size = 37)], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "BlogId" = @p0, "Content" = @p1, "Title" = @p2
WHERE "Id" = @p3;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p3='2' (DbType = String), @p0='1' (DbType = String), @p1='F# 5 is the latest version of F#, the functional programming language...' (Size = 72), @p2='Announcing F# 5' (Size = 15)], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "BlogId" = @p0, "Content" = @p1, "Title" = @p2
WHERE "Id" = @p3;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p0='1' (DbType = String), @p1='.NET 5.0 includes many enhancements, including single file applications, more...' (Size = 80), @p2='Announcing .NET 5.0' (Size = 19)], 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();
Jedná se o velmi snadný způsob generování aktualizací a vkládání z odpojeného grafu. Výsledkem však jsou aktualizace nebo vložení odesílané do databáze pro každou vlastnost každé sledované entity, i když některé hodnoty vlastností nebyly změněny. Nebudějte tím příliš vyděšený; pro mnoho aplikací s malými grafy to může být snadný a praktický způsob generování aktualizací. To znamená, že jiné složitější vzory můžou někdy vést k efektivnějším aktualizacím, jak je popsáno v řešení identity v EF Core.
Odstranění existujících entit
Aby byla entita odstraněna nástrojem SaveChanges, musí být sledována Deleted
ve stavu. Entity jsou obvykle vloženy do Deleted
stavu voláním jednoho z DbContext.Remove, DbContext.RemoveRangenebo ekvivalentní metody on DbSet<TEntity>. Pokud chcete například označit existující příspěvek jako Deleted
:
context.Remove(
new Post { Id = 2 });
Kontrola zobrazení ladění sledování změn po tomto volání ukazuje, že kontext sleduje entitu Deleted
ve stavu:
Post {Id: 2} Deleted
Id: 2 PK
BlogId: <null> FK
Content: <null>
Title: <null>
Blog: <null>
Tato entita bude odstraněna při zavolání SaveChanges. Například při použití SQLite:
-- Executed DbCommand (0ms) [Parameters=[@p0='2' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Posts"
WHERE "Id" = @p0;
SELECT changes();
Po dokončení saveChanges je odstraněná entita odpojena od DbContext, protože již v databázi neexistuje. Zobrazení ladění je proto prázdné, protože se nesledují žádné entity.
Odstranění závislých nebo podřízených entit
Odstranění závislých nebo podřízených entit z grafu je jednodušší než odstranění objektů zabezpečení nebo nadřazených entit. Další informace najdete v další části a změna cizích klíčů a navigace.
Je neobvyklé volat Remove
entitu vytvořenou pomocí new
. Navíc, na rozdíl od Add
Attach
a Update
, je neobvyklé volat Remove
entitu, která ještě není sledována Unchanged
v nebo Modified
stavu. Místo toho je typické sledovat jednu entitu nebo graf souvisejících entit a pak volat Remove
entity, které by se měly odstranit. Tento graf sledovaných entit se obvykle vytváří takto:
- Spuštění dotazu pro entity
Attach
Použití neboUpdate
metod v grafu odpojených entit, jak je popsáno v předchozích částech.
Například kód v předchozí části bude pravděpodobně získat příspěvek od klienta a pak udělat něco takového:
context.Attach(post);
context.Remove(post);
Chová se přesně stejně jako v předchozím příkladu, protože volání Remove
nesledované entity způsobí, že se nejprve připojí a pak označí jako Deleted
.
V realističtějších příkladech se nejprve připojí graf entit a některé z těchto entit se označí jako odstraněné. Příklad:
// Attach a blog and associated posts
context.Attach(blog);
// Mark one post as Deleted
context.Remove(blog.Posts[1]);
Všechny entity jsou označené jako Unchanged
, s výjimkou těch, u kterých Remove
bylo voláno:
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
Posts: [{Id: 1}, {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}
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}
Tato entita bude odstraněna při zavolání SaveChanges. Například při použití SQLite:
-- Executed DbCommand (0ms) [Parameters=[@p0='2' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Posts"
WHERE "Id" = @p0;
SELECT changes();
Po dokončení saveChanges je odstraněná entita odpojena od DbContext, protože již v databázi neexistuje. Ostatní entity zůstávají ve Unchanged
stavu:
Blog {Id: 1} Unchanged
Id: 1 PK
Name: '.NET Blog'
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}
Odstranění objektů zabezpečení nebo nadřazených entit
Každá relace, která spojuje dva typy entit, má hlavní nebo nadřazený konec a závislý nebo podřízený konec. Závislá/podřízená entita je entita s vlastností cizího klíče. V relaci 1:N je objekt zabezpečení nebo nadřazený objekt na straně 1 a závislý/podřízený je na straně N. Další informace najdete v tématu Relace .
V předchozích příkladech jsme odstranili příspěvek, což je závislá/podřízená entita v relaci 1:N blogu. To je poměrně jednoduché, protože odebrání závislé/podřízené entity nemá žádný vliv na jiné entity. Na druhou stranu odstranění objektu zabezpečení nebo nadřazené entity musí mít vliv také na všechny závislé/podřízené entity. Nepoužívejte tak hodnotu cizího klíče odkazující na hodnotu primárního klíče, která již neexistuje. Jedná se o neplatný stav modelu a ve většině databází dojde k chybě referenčního omezení.
Tento neplatný stav modelu lze zpracovat dvěma způsoby:
- Nastavení hodnot FK na hodnotu null To znamená, že závislé osoby a podřízené položky už nesouvisí s žádným objektem zabezpečení nebo nadřazeným objektem. Toto je výchozí hodnota pro volitelné relace, ve kterých musí být cizí klíč null. Nastavení klíče FK na hodnotu null není platné pro požadované relace, kde cizí klíč je obvykle nenulový.
- Odstranění závislých/podřízených položek Toto je výchozí hodnota pro požadované relace a je také platná pro volitelné relace.
Podrobné informace o sledování změn a relacích najdete v tématu Změna cizích klíčů a navigace.
Volitelné relace
Vlastnost cizího Post.BlogId
klíče je v modelu, který jsme používali, null. To znamená, že relace je volitelná, a proto výchozí chování EF Core je nastavit BlogId
vlastnosti cizího klíče na hodnotu null při odstranění blogu. Příklad:
// Attach a blog and associated posts
context.Attach(blog);
// Mark the blog as deleted
context.Remove(blog);
Kontrola zobrazení ladění sledování změn po volání Remove
ukazuje, že podle očekávání je blog nyní označen jako Deleted
:
Blog {Id: 1} Deleted
Id: 1 PK
Name: '.NET Blog'
Posts: [{Id: 1}, {Id: 2}]
Post {Id: 1} Modified
Id: 1 PK
BlogId: <null> FK Modified Originally 1
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: <null>
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>
Zajímavější je, že všechny související příspěvky jsou nyní označeny jako Modified
. Důvodem je to, že vlastnost cizího klíče v každé entitě byla nastavena na hodnotu null. Volání SaveChanges aktualizuje hodnotu cizího klíče pro každý příspěvek na hodnotu null v databázi před odstraněním blogu:
-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p0=NULL], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "BlogId" = @p0
WHERE "Id" = @p1;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p1='2' (DbType = String), @p0=NULL], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "BlogId" = @p0
WHERE "Id" = @p1;
SELECT changes();
-- Executed DbCommand (0ms) [Parameters=[@p2='1' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Blogs"
WHERE "Id" = @p2;
SELECT changes();
Po dokončení saveChanges je odstraněná entita odpojena od DbContext, protože již v databázi neexistuje. Ostatní entity jsou teď označené jako Unchanged
hodnoty cizího klíče null, které odpovídají stavu databáze:
Post {Id: 1} Unchanged
Id: 1 PK
BlogId: <null> FK
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: <null>
Post {Id: 2} Unchanged
Id: 2 PK
BlogId: <null> FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5'
Blog: <null>
Požadované relace
Pokud je vlastnost cizího Post.BlogId
klíče nenulová, stane se vztah mezi blogy a příspěvky "povinné". V takovém případě EF Core ve výchozím nastavení odstraní závislé/podřízené entity při odstranění objektu zabezpečení nebo nadřazeného objektu. Například odstranění blogu se souvisejícími příspěvky jako v předchozím příkladu:
// Attach a blog and associated posts
context.Attach(blog);
// Mark the blog as deleted
context.Remove(blog);
Kontrola zobrazení ladění sledování změn po volání Remove
ukazuje, že podle očekávání je blog znovu označen jako Deleted
:
Blog {Id: 1} Deleted
Id: 1 PK
Name: '.NET Blog'
Posts: [{Id: 1}, {Id: 2}]
Post {Id: 1} Deleted
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}
Zajímavější v tomto případě je, že všechny související příspěvky byly také označeny jako Deleted
. Volání funkce SaveChanges způsobí odstranění blogu a všech souvisejících příspěvků z databáze:
-- Executed DbCommand (0ms) [Parameters=[@p0='1' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Posts"
WHERE "Id" = @p0;
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=[@p1='1' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Blogs"
WHERE "Id" = @p1;
Po dokončení funkce SaveChanges se všechny odstraněné entity odpojí od DbContext, protože už v databázi neexistují. Výstup ze zobrazení ladění je proto prázdný.
Poznámka
Tento dokument poškrábe povrch jenom při práci s relacemi v EF Core. Další informace o relacích modelování a změně cizích klíčů a navigace najdete v tématu Relace, kde najdete další informace o aktualizaci nebo odstranění závislých/podřízených entit při volání SaveChanges.
Vlastní sledování s využitím TrackGraphu
ChangeTracker.TrackGraph funguje jako Add
a Attach
Update
s tím rozdílem, že generuje zpětné volání pro každou instanci entity před sledováním. To umožňuje použít vlastní logiku při určování způsobu sledování jednotlivých entit v grafu.
Představte si například pravidlo, které EF Core používá při sledování entit s vygenerovanými hodnotami klíče: pokud je hodnota klíče nula, pak je entita nová a měla by být vložena. Pojďme toto pravidlo rozšířit tak, aby řeklo, jestli je hodnota klíče záporná, pak by se měla entita odstranit. To nám umožňuje změnit hodnoty primárního klíče v entitách odpojeného grafu tak, aby označovaly odstraněné entity:
blog.Posts.Add(
new Post
{
Title = "Announcing .NET 5.0",
Content = ".NET 5.0 includes many enhancements, including single file applications, more..."
}
);
var toDelete = blog.Posts.Single(e => e.Title == "Announcing F# 5");
toDelete.Id = -toDelete.Id;
Tento odpojený graf se pak dá sledovat pomocí TrackGraphu:
public static async Task UpdateBlog(Blog blog)
{
using var context = new BlogsContext();
context.ChangeTracker.TrackGraph(
blog, node =>
{
var propertyEntry = node.Entry.Property("Id");
var keyValue = (int)propertyEntry.CurrentValue;
if (keyValue == 0)
{
node.Entry.State = EntityState.Added;
}
else if (keyValue < 0)
{
propertyEntry.CurrentValue = -keyValue;
node.Entry.State = EntityState.Deleted;
}
else
{
node.Entry.State = EntityState.Modified;
}
Console.WriteLine($"Tracking {node.Entry.Metadata.DisplayName()} with key value {keyValue} as {node.Entry.State}");
});
await context.SaveChangesAsync();
}
Pro každou entitu v grafu kód výše zkontroluje hodnotu primárního klíče před sledováním entity. Pro hodnoty klíče bez sady (nula) kód dělá to, co EF Core normálně dělá. To znamená, že pokud klíč není nastavený, je entita označena jako Added
. Pokud je klíč nastaven a hodnota není záporná, je entita označena jako Modified
. Pokud se však najde záporná hodnota klíče, obnoví se její skutečná nezáporná hodnota a entita se bude sledovat jako Deleted
.
Výstupem spuštění tohoto kódu je:
Tracking Blog with key value 1 as Modified
Tracking Post with key value 1 as Modified
Tracking Post with key value -2 as Deleted
Tracking Post with key value 0 as Added
Poznámka
Pro zjednodušení tento kód předpokládá, že každá entita má celočíselnou vlastnost primárního klíče volanou Id
. To může být kodifikováno do abstraktní základní třídy nebo rozhraní. Případně lze z metadat získat IEntityType vlastnost nebo vlastnosti primárního klíče, aby tento kód fungoval s libovolným typem entity.
TrackGraph má dvě přetížení. V jednoduchém přetížení použitém výše ef Core určuje, kdy se má graf zastavit. Konkrétně přestane navštěvovat nové související entity z dané entity, pokud je tato entita již sledována, nebo když zpětné volání nezačne sledovat entitu.
Pokročilá přetížení , ChangeTracker.TrackGraph<TState>(Object, TState, Func<EntityEntryGraphNode<TState>,Boolean>)má zpětné volání, které vrací logickou hodnotu. Pokud zpětné volání vrátí hodnotu false, procházení grafu se zastaví, jinak bude pokračovat. Při použití tohoto přetížení je třeba dbát na to, aby nedocházelo k nekonečným smyčkám.
Rozšířené přetížení také umožňuje zadat stav TrackGraph a tento stav se pak předá každému zpětnému volání.