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:

  1. Vytváření nových entit, které budou vloženy do databáze.
  2. 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 Addve 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 ModifiedUnchanged , ž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 Attachve 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 AddAttach 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:

  1. Spuštění dotazu pro entity
  2. Attach Použití nebo Update 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:

  1. 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ý.
  2. 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 Adda AttachUpdate 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 void 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}");
        });

    context.SaveChanges();
}

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í.