Odpojené entity

Instance DbContext automaticky bude sledovat entity vrácené z databáze. Změny provedené v těchto entitách se pak zjistí při zavolání SaveChanges a databáze se podle potřeby aktualizuje. Podrobnosti najdete v tématu Základní ukládání a související data .

Někdy se ale entity dotazují pomocí jedné kontextové instance a pak se uloží pomocí jiné instance. K tomu často dochází v odpojených scénářích, jako je webová aplikace, kde se entity dotazují, odesílají klientovi, upravují, odesílají se zpět na server v požadavku a pak se uloží. V tomto případě musí druhá instance kontextu vědět, jestli jsou entity nové (měly by být vloženy) nebo existující (měly by být aktualizovány).

Tip

Ukázku pro tento článek najdete na GitHubu.

Tip

EF Core může sledovat pouze jednu instanci jakékoli entity s danou hodnotou primárního klíče. Nejlepším způsobem, jak se tomu vyhnout, je použití krátkodobého kontextu pro každou jednotku práce, aby byl kontext spuštěn prázdný, obsahuje entity, uloží tyto entity a pak se kontext odstraní a zahodí.

Identifikace nových entit

Klient identifikuje nové entity.

Nejjednodušším případem řešení je, když klient informuje server, jestli je entita nová nebo existující. Často se například požadavek na vložení nové entity liší od požadavku na aktualizaci existující entity.

Zbývající část této části popisuje případy, kdy je nutné určit jiným způsobem, zda vložit nebo aktualizovat.

S automaticky generovanými klíči

Hodnotu automaticky vygenerovaného klíče lze často použít k určení, jestli je potřeba vložit nebo aktualizovat entitu. Pokud klíč nebyl nastaven (to znamená, že stále má výchozí hodnotu CLR null, nula atd.), musí být entita nová a musí ji vložit. Pokud je naopak nastavená hodnota klíče, musí už být uložená a teď potřebuje aktualizaci. Jinými slovy, pokud má klíč hodnotu, pak se entita dotazovala, odeslala klientovi a vrátila se k aktualizaci.

Pokud je typ entity známý, je snadné zkontrolovat nesmnožený klíč:

public static bool IsItNew(Blog blog)
    => blog.BlogId == 0;

Ef ale také nabízí integrovaný způsob, jak to udělat pro libovolný typ entity a typ klíče:

public static bool IsItNew(DbContext context, object entity)
    => !context.Entry(entity).IsKeySet;

Tip

Klíče se nastaví, jakmile budou entity sledovány kontextem, i když je entita ve stavu Přidání. To pomáhá při procházení grafu entit a rozhodování o tom, co s jednotlivými entitami dělat, například při používání rozhraní API TrackGraph. Hodnota klíče by měla být použita pouze způsobem uvedeným zde před provedením jakéhokoli volání ke sledování entity.

S dalšími klíči

K identifikaci nových entit je potřeba nějaký jiný mechanismus, když se hodnoty klíče negenerují automaticky. Existují dva obecné přístupy k tomuto:

  • Dotaz na entitu
  • Předání příznaku z klienta

K dotazování na entitu stačí použít metodu Find:

public static bool IsItNew(BloggingContext context, Blog blog)
    => context.Blogs.Find(blog.BlogId) == null;

Je nad rámec tohoto dokumentu, aby se zobrazil úplný kód pro předávání příznaku z klienta. Ve webové aplikaci to obvykle znamená provádění různých požadavků na různé akce nebo předání nějakého stavu v požadavku a následné extrakci v kontroleru.

Ukládání jednotlivých entit

Pokud víte, jestli je potřeba vložit nebo aktualizovat, můžete použít možnost Přidat nebo Aktualizovat odpovídajícím způsobem:

public static void Insert(DbContext context, object entity)
{
    context.Add(entity);
    context.SaveChanges();
}

public static void Update(DbContext context, object entity)
{
    context.Update(entity);
    context.SaveChanges();
}

Pokud ale entita používá hodnoty automaticky generovaného klíče, lze metodu Update použít v obou případech:

public static void InsertOrUpdate(DbContext context, object entity)
{
    context.Update(entity);
    context.SaveChanges();
}

Metoda Update obvykle označuje entitu pro aktualizaci, nikoli vložení. Pokud má ale entita automaticky vygenerovaný klíč a nebyla nastavena žádná hodnota klíče, je entita místo toho automaticky označena pro vložení.

Pokud entita nepoužívá automaticky generované klíče, aplikace se musí rozhodnout, jestli se má entita vložit nebo aktualizovat: Například:

public static void InsertOrUpdate(BloggingContext context, Blog blog)
{
    var existingBlog = context.Blogs.Find(blog.BlogId);
    if (existingBlog == null)
    {
        context.Add(blog);
    }
    else
    {
        context.Entry(existingBlog).CurrentValues.SetValues(blog);
    }

    context.SaveChanges();
}

Tady jsou kroky:

  • Pokud funkce Najít vrátí hodnotu null, databáze ještě neobsahuje blog s tímto ID, takže zavoláme přidat značku pro vložení.
  • Pokud funkce Najít vrátí entitu, existuje v databázi a kontext teď sleduje existující entitu.
    • Pak pomocí SetValues nastavíme hodnoty pro všechny vlastnosti této entity na ty, které pocházejí z klienta.
    • Volání SetValues označí entitu, která se má podle potřeby aktualizovat.

Tip

SetValues označí pouze jako změněné vlastnosti, které mají jiné hodnoty než ty ve sledované entitě. To znamená, že po odeslání aktualizace budou aktualizovány pouze sloupce, které se skutečně změnily. (A pokud se nic nezměnilo, nebude se vůbec odesílat žádná aktualizace.)

Práce s grafy

Rozlišení identity

Jak jsme uvedli výše, EF Core může sledovat pouze jednu instanci jakékoli entity s danou hodnotou primárního klíče. Při práci s grafy by měl být graf ideálně vytvořen tak, aby byl tento invariant zachován a kontext by se měl použít pouze pro jednu jednotku práce. Pokud graf obsahuje duplicity, bude nutné graf před odesláním do EF zpracovat a konsolidovat více instancí do jednoho. To nemusí být triviální, pokud instance mají konfliktní hodnoty a relace, takže sloučení duplicit by se mělo provést co nejdříve v kanálu aplikace, aby se zabránilo řešení konfliktů.

Všechny nové nebo všechny existující entity

Příkladem práce s grafy je vložení nebo aktualizace blogu společně s jeho kolekcí přidružených příspěvků. Pokud by měly být vloženy všechny entity v grafu nebo všechny by měly být aktualizovány, je proces stejný, jak je popsáno výše u jednotlivých entit. Například graf blogů a příspěvků vytvořených takto:

var blog = new Blog
{
    Url = "http://sample.com", Posts = new List<Post> { new Post { Title = "Post 1" }, new Post { Title = "Post 2" }, }
};

lze vložit takto:

public static void InsertGraph(DbContext context, object rootEntity)
{
    context.Add(rootEntity);
    context.SaveChanges();
}

Volání přidat označí blog a všechny příspěvky, které se mají vložit.

Podobně platí, že pokud je potřeba aktualizovat všechny entity v grafu, můžete použít aktualizaci:

public static void UpdateGraph(DbContext context, object rootEntity)
{
    context.Update(rootEntity);
    context.SaveChanges();
}

Blog a všechny jeho příspěvky budou označeny k aktualizaci.

Kombinace nových a existujících entit

S automaticky generovanými klíči je možné aktualizaci znovu použít pro vložení i aktualizace, i když graf obsahuje kombinaci entit, které vyžadují vložení, a pro ty, které vyžadují aktualizaci:

public static void InsertOrUpdateGraph(DbContext context, object rootEntity)
{
    context.Update(rootEntity);
    context.SaveChanges();
}

Aktualizace označí všechny entity v grafu, blogu nebo příspěvku pro vložení, pokud nemá nastavenou hodnotu klíče, zatímco všechny ostatní entity jsou označené k aktualizaci.

Stejně jako v případě, že nepoužíváte automaticky generované klíče, můžete použít dotaz a některé zpracování:

public static void InsertOrUpdateGraph(BloggingContext context, Blog blog)
{
    var existingBlog = context.Blogs
        .Include(b => b.Posts)
        .FirstOrDefault(b => b.BlogId == blog.BlogId);

    if (existingBlog == null)
    {
        context.Add(blog);
    }
    else
    {
        context.Entry(existingBlog).CurrentValues.SetValues(blog);
        foreach (var post in blog.Posts)
        {
            var existingPost = existingBlog.Posts
                .FirstOrDefault(p => p.PostId == post.PostId);

            if (existingPost == null)
            {
                existingBlog.Posts.Add(post);
            }
            else
            {
                context.Entry(existingPost).CurrentValues.SetValues(post);
            }
        }
    }

    context.SaveChanges();
}

Zpracování odstranění

Odstranění může být obtížné zpracovat, protože často absence entity znamená, že by měla být odstraněna. Jedním ze způsobů, jak to vyřešit, je použít "obnovitelné odstranění" tak, aby entita byla označena jako odstraněná místo toho, aby se skutečně odstranila. Odstranění se pak změní na stejné jako aktualizace. Obnovitelné odstranění je možné implementovat pomocí filtrů dotazů.

U skutečných odstranění je běžným vzorem použití rozšíření vzoru dotazu k provádění toho, co je v podstatě rozdíl grafu. Příklad:

public static void InsertUpdateOrDeleteGraph(BloggingContext context, Blog blog)
{
    var existingBlog = context.Blogs
        .Include(b => b.Posts)
        .FirstOrDefault(b => b.BlogId == blog.BlogId);

    if (existingBlog == null)
    {
        context.Add(blog);
    }
    else
    {
        context.Entry(existingBlog).CurrentValues.SetValues(blog);
        foreach (var post in blog.Posts)
        {
            var existingPost = existingBlog.Posts
                .FirstOrDefault(p => p.PostId == post.PostId);

            if (existingPost == null)
            {
                existingBlog.Posts.Add(post);
            }
            else
            {
                context.Entry(existingPost).CurrentValues.SetValues(post);
            }
        }

        foreach (var post in existingBlog.Posts)
        {
            if (!blog.Posts.Any(p => p.PostId == post.PostId))
            {
                context.Remove(post);
            }
        }
    }

    context.SaveChanges();
}

TrackGraph

Interně, přidat, připojit a aktualizovat používat procházení grafů s určením, jestli se má označit jako Přidané (vložení), Změněno (aktualizace), Beze změny (nic) nebo Odstraněno (odstranit). Tento mechanismus je přístupný prostřednictvím rozhraní API TrackGraph. Předpokládejme například, že když klient odešle zpět graf entit, nastaví u každé entity příznak označující, jak se má zpracovat. TrackGraph pak můžete použít ke zpracování tohoto příznaku:

public static void SaveAnnotatedGraph(DbContext context, object rootEntity)
{
    context.ChangeTracker.TrackGraph(
        rootEntity,
        n =>
        {
            var entity = (EntityBase)n.Entry.Entity;
            n.Entry.State = entity.IsNew
                ? EntityState.Added
                : entity.IsChanged
                    ? EntityState.Modified
                    : entity.IsDeleted
                        ? EntityState.Deleted
                        : EntityState.Unchanged;
        });

    context.SaveChanges();
}

Příznaky se zobrazí pouze jako součást entity kvůli jednoduchosti příkladu. Příznaky by obvykle byly součástí DTO nebo některého jiného stavu zahrnutého v požadavku.