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
Navigace a související entity
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/ExecuteUpdate
ExecuteDelete
.
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í ExecuteUpdate
dotazu 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 aniExecuteDelete
. - 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
aExecuteDelete
.