Sdílet prostřednictvím


ExecuteUpdate a ExecuteDelete

Poznámka:

Tato funkce byla představena v EF Core 7.0.

ExecuteUpdate a ExecuteDelete představují způsob, jak ukládat data do databáze bez použití tradičního sledování změn a SaveChanges() metody EF. Úvodní porovnání těchto dvou technik najdete na stránce Přehled při ukládání dat.

ExecuteDelete

Předpokládejme, že potřebujete odstranit všechny blogy s hodnocením pod určitou prahovou hodnotou. Tradiční SaveChanges() přístup vyžaduje, abyste udělali toto:

foreach (var blog in context.Blogs.Where(b => b.Rating < 3))
{
    context.Blogs.Remove(blog);
}

context.SaveChanges();

To je poměrně neefektivní způsob, jak provést tuto úlohu: dotazujeme databázi pro všechny blogy, které odpovídají našemu filtru, a pak dotazujeme, materializujeme a sledujeme všechny tyto instance; Počet odpovídajících entit může být obrovský. Poté řekneme EF sledování změn, že každý blog musí být odebrán, a použít tyto změny voláním SaveChanges(), který generuje DELETE příkaz pro každý z nich a každý z nich.

Tady je stejná úloha prováděná prostřednictvím ExecuteDelete rozhraní API:

context.Blogs.Where(b => b.Rating < 3).ExecuteDelete();

Používá známé operátory LINQ k určení, které blogy by měly být ovlivněny – stejně jako kdybyste je dotazovali – a pak EF řekne, aby spustil SQL DELETE pro databázi:

DELETE FROM [b]
FROM [Blogs] AS [b]
WHERE [b].[Rating] < 3

Kromě jednodušších a kratších se to v databázi provádí velmi efektivně, aniž by se načítala data z databáze nebo zahrnovala sledování změn EF. Všimněte si, že můžete použít libovolné operátory LINQ k výběru blogů, které chcete odstranit – ty jsou přeloženy do SQL pro spuštění v databázi, stejně jako kdybyste dotazovali tyto blogy ven.

ExecuteUpdate

Místo odstranění těchto blogů, co když bychom chtěli změnit vlastnost, aby bylo možné označit, že by měly být skryté? ExecuteUpdate poskytuje podobný způsob vyjádření příkazu SQL UPDATE :

context.Blogs
    .Where(b => b.Rating < 3)
    .ExecuteUpdate(setters => setters.SetProperty(b => b.IsVisible, false));

Stejně jako u ExecuteDelete, nejprve používáme LINQ k určení, které blogy mají být ovlivněny; ale s ExecuteUpdate tím, že musíme také vyjádřit změnu, která se má použít u odpovídajících blogů. To se provádí voláním SetProperty v rámci ExecuteUpdate volání a poskytnutím dvou argumentů: vlastnosti, kterou chcete změnit (IsVisible) a novou hodnotu, kterou má mít (false). To způsobí, že se spustí následující SQL:

UPDATE [b]
SET [b].[IsVisible] = CAST(0 AS bit)
FROM [Blogs] AS [b]
WHERE [b].[Rating] < 3

Aktualizace více vlastností

ExecuteUpdate umožňuje aktualizovat více vlastností v jediném vyvolání. Pokud například chcete nastavit IsVisible hodnotu false i nastavit Rating na nulu, jednoduše zřetězte další SetProperty volání:

context.Blogs
    .Where(b => b.Rating < 3)
    .ExecuteUpdate(setters => setters
        .SetProperty(b => b.IsVisible, false)
        .SetProperty(b => b.Rating, 0));

Provede se následující příkaz SQL:

UPDATE [b]
SET [b].[Rating] = 0,
    [b].[IsVisible] = CAST(0 AS bit)
FROM [Blogs] AS [b]
WHERE [b].[Rating] < 3

Odkazování na existující hodnotu vlastnosti

Výše uvedené příklady aktualizovaly vlastnost na novou konstantní hodnotu. ExecuteUpdate umožňuje také odkazovat na existující hodnotu vlastnosti při výpočtu nové hodnoty; Pokud chcete například zvýšit hodnocení všech odpovídajících blogů o jednu, použijte následující:

context.Blogs
    .Where(b => b.Rating < 3)
    .ExecuteUpdate(setters => setters.SetProperty(b => b.Rating, b => b.Rating + 1));

Všimněte si, že druhý argument, který SetProperty má být nyní funkcí lambda, a nikoli konstantou jako předtím. Jeho b parametr představuje aktualizaci blogu. V rámci této lambda, b.Rating tedy obsahuje hodnocení před případnou změnou. Provede se následující příkaz SQL:

UPDATE [b]
SET [b].[Rating] = [b].[Rating] + 1
FROM [Blogs] AS [b]
WHERE [b].[Rating] < 3

ExecuteUpdate v současné době nepodporuje odkazování na navigace v rámci SetProperty lambda. Řekněme například, že chceme aktualizovat všechna hodnocení blogů tak, aby nové hodnocení každého blogu bylo průměrem všech hodnocení příspěvků. Můžeme se pokusit použít ExecuteUpdate takto:

context.Blogs.ExecuteUpdate(
    setters => setters.SetProperty(b => b.Rating, b => b.Posts.Average(p => p.Rating)));

Ef však umožňuje provést tuto operaci tak, že nejprve použije Select k výpočtu průměrného hodnocení a promítá ho do anonymního typu a pak použije ExecuteUpdate toto:

context.Blogs
    .Select(b => new { Blog = b, NewRating = b.Posts.Average(p => p.Rating) })
    .ExecuteUpdate(setters => setters.SetProperty(b => b.Blog.Rating, b => b.NewRating));

Provede se následující příkaz SQL:

UPDATE [b]
SET [b].[Rating] = CAST((
    SELECT AVG(CAST([p].[Rating] AS float))
    FROM [Post] AS [p]
    WHERE [b].[Id] = [p].[BlogId]) AS int)
FROM [Blogs] AS [b]

Sledování změn

Uživatelé, kteří SaveChanges znají, se používají k provádění více změn a volání SaveChanges , aby použili všechny tyto změny v databázi. To je možné sledováním změn EF, který tyto změny shromažďuje nebo sleduje.

ExecuteUpdate a ExecuteDelete pracují zcela odlišně: projeví se okamžitě, v okamžiku, kdy jsou vyvolány. To znamená, že zatímco jedna ExecuteUpdate nebo ExecuteDelete operace může ovlivnit mnoho řádků, není možné hromadit více takových operací a použít je najednou, například při volání SaveChanges. Ve skutečnosti funkce vůbec neví o sledování změn EF a nemají s ním žádnou interakci. To má několik důležitých důsledků.

Uvažujte následující kód:

// 1. Query the blog with the name `SomeBlog`. Since EF queries are tracking by default, the Blog is now tracked by EF's change tracker.
var blog = context.Blogs.Single(b => b.Name == "SomeBlog");

// 2. Increase the rating of all blogs in the database by one. This executes immediately.
context.Blogs.ExecuteUpdate(setters => setters.SetProperty(b => b.Rating, b => b.Rating + 1));

// 3. Increase the rating of `SomeBlog` by two. This modifies the .NET `Rating` property and is not yet persisted to the database.
blog.Rating += 2;

// 4. Persist tracked changes to the database.
context.SaveChanges();

Zásadní je, že při ExecuteUpdate vyvolání a všechny blogy se aktualizují v databázi, sledování změn EF se neaktualizuje a sledovaný instance .NET má stále původní hodnotu hodnocení od okamžiku, kdy byl dotazován. Předpokládejme, že hodnocení blogu bylo původně 5; po spuštění třetího řádku je hodnocení v databázi nyní 6 (z důvodu ExecuteUpdate), zatímco hodnocení ve sledované instanci .NET je 7. Při SaveChanges zavolání ef zjistí, že nová hodnota 7 se liší od původní hodnoty 5 a tato změna se zachová. Změna provedená přepsáním ExecuteUpdate a nezapočítáním se.

V důsledku toho je obvykle vhodné vyhnout se kombinování sledovaných SaveChanges úprav i nesledovaných úprav prostřednictvím/ExecuteUpdateExecuteDelete .

Transakce

Pokračování na výše uvedeném je důležité pochopit, že ExecuteUpdate a ExecuteDelete nespustí implicitně transakci, kterou jsou vyvolány. Uvažujte následující kód:

context.Blogs.ExecuteUpdate(/* some update */);
context.Blogs.ExecuteUpdate(/* another update */);

var blog = context.Blogs.Single(b => b.Name == "SomeBlog");
blog.Rating += 2;
context.SaveChanges();

Každé ExecuteUpdate volání způsobí odeslání jednoho SQL UPDATE do databáze. Vzhledem k tomu, že se nevytvoří žádná transakce, pokud jakýkoli druh selhání brání druhému ExecuteUpdate v úspěšném dokončení, účinky první transakce jsou stále zachovány do databáze. Ve skutečnosti čtyři výše uvedené operace – dvě vyvolání ExecuteUpdatedotazu a SaveChanges - každý provede v rámci své vlastní transakce. Pokud chcete zabalit více operací do jedné transakce, explicitně zahajte transakci pomocí DatabaseFacade:

using (var transaction = context.Database.BeginTransaction())
{
    context.Blogs.ExecuteUpdate(/* some update */);
    context.Blogs.ExecuteUpdate(/* another update */);

    ...
}

Další informace o zpracování transakcí naleznete v tématu Použití transakcí.

Řízení souběžnosti a ovlivněné řádky

SaveChanges poskytuje automatické řízení souběžnosti pomocí tokenu souběžnosti, aby se zajistilo, že se řádek nezměnil mezi okamžikem, kdy jste ho načetli, a okamžikem, kdy do něj uložíte změny. Vzhledem k tomu ExecuteUpdate , že a ExecuteDelete nepracují s sledováním změn, nemůžou automaticky použít řízení souběžnosti.

Obě tyto metody však vrátí počet řádků, které byly ovlivněny operací; to může být užitečné zejména pro implementaci řízení souběžnosti sami:

// (load the ID and concurrency token for a Blog in the database)

var numUpdated = context.Blogs
    .Where(b => b.Id == id && b.ConcurrencyToken == concurrencyToken)
    .ExecuteUpdate(/* ... */);
if (numUpdated == 0)
{
    throw new Exception("Update failed!");
}

V tomto kódu použijeme operátor LINQ Where k použití aktualizace na konkrétní blog a pouze v případě, že má token souběžnosti konkrétní hodnotu (např. ten, který jsme viděli při dotazování blogu z databáze). Potom zkontrolujeme, kolik řádků se skutečně aktualizovalo ExecuteUpdate. Pokud je výsledek nula, nebyly aktualizovány žádné řádky a token souběžnosti se pravděpodobně změnil v důsledku souběžné aktualizace.

Omezení

  • V současné době se podporuje pouze aktualizace a odstraňování; vložení musí být provedeno prostřednictvím DbSet<TEntity>.Add a SaveChanges().
  • Příkazy SQL UPDATE a DELETE sice umožňují načíst hodnoty původního sloupce pro ovlivněné řádky, ale v současné době ExecuteUpdate to nepodporuje ani ExecuteDelete.
  • Více vyvolání těchto metod nelze dávkot. Každé vyvolání provádí do databáze vlastní odezvu.
  • Databáze obvykle umožňují úpravu pouze jedné tabulky pomocí funkce UPDATE nebo DELETE.
  • Tyto metody v současné době pracují pouze s zprostředkovateli relačních databází.

Další materiály

  • Relace komunitního přístupu k datům .NET, kde probereme ExecuteUpdate a ExecuteDelete.