Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
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řehledu o 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:
await foreach (var blog in context.Blogs.Where(b => b.Rating < 3).AsAsyncEnumerable())
{
context.Blogs.Remove(blog);
}
await context.SaveChangesAsync();
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 sledovači změn EF, že každý blog musí být odebrán, a aplikujeme tyto změny voláním SaveChanges(), který generuje DELETE příkaz pro každý z nich.
Tady je stejná úloha prováděná prostřednictvím ExecuteDelete rozhraní API:
await context.Blogs.Where(b => b.Rating < 3).ExecuteDeleteAsync();
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ě toho, že je jednodušší a kratší, provádí se tato operace v databázi velmi efektivně, aniž by docházelo k načítání jakýchkoli dat z databáze nebo k zapojení sledování změn v 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.
SpustitAktualizaci
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 :
await context.Blogs
.Where(b => b.Rating < 3)
.ExecuteUpdateAsync(setters => setters.SetProperty(b => b.IsVisible, false));
Stejně jako u ExecuteDelete, nejprve používáme LINQ k určení, jaké blogy budou ovlivněny, ale u ExecuteUpdate musíme také specifikovat změnu, která se má použít na odpovídající Blogy. 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í:
await context.Blogs
.Where(b => b.Rating < 3)
.ExecuteUpdateAsync(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í:
await context.Blogs
.Where(b => b.Rating < 3)
.ExecuteUpdateAsync(setters => setters.SetProperty(b => b.Rating, b => b.Rating + 1));
Všimněte si, že druhý argument u SetProperty je nyní funkcí lambda, a nikoli konstantou jako dříve. 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:
await context.Blogs.ExecuteUpdateAsync(
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žijete Select k výpočtu průměrného hodnocení a promítnete ho do anonymního typu, a poté použijete ExecuteUpdate na tento anonymní typ.
await context.Blogs
.Select(b => new { Blog = b, NewRating = b.Posts.Average(p => p.Rating) })
.ExecuteUpdateAsync(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í, jsou zvyklí provádět více změn a poté volat SaveChanges k jejich aplikaci na databázi; to umožňuje systém sledování 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 = await context.Blogs.SingleAsync(b => b.Name == "SomeBlog");
// 2. Increase the rating of all blogs in the database by one. This executes immediately.
await context.Blogs.ExecuteUpdateAsync(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.
await context.SaveChangesAsync();
Zásadní je, že když je ExecuteUpdate vyvolán a všechny blogy jsou aktualizovány v databázi, sledování změn EF se neaktualizuje a sledovaná instance .NET si stále uchovává původní hodnotu hodnocení z okamžiku, kdy byla dotazována. 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á ExecuteUpdate je přepsána a není brána v úvahu.
V důsledku toho je obvykle vhodné vyhnout se kombinování sledovaných SaveChanges úprav a nesledovaných úprav prostřednictvím ExecuteUpdate/ExecuteDelete.
Transakce
Pokračujme v tom, co bylo uvedeno výše, je důležité pochopit, že ExecuteUpdate a ExecuteDelete implicitně nezahajují transakci při jejich vyvolání. Uvažujte následující kód:
await context.Blogs.ExecuteUpdateAsync(/* some update */);
await context.Blogs.ExecuteUpdateAsync(/* another update */);
var blog = await context.Blogs.SingleAsync(b => b.Name == "SomeBlog");
blog.Rating += 2;
await context.SaveChangesAsync();
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, jeden dotaz a SaveChanges – jsou každá spuštěna 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í.
Kontrola souběžnosti a změně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, že ExecuteUpdate a ExecuteDelete neinteragují se sledovačem změn, nemohou automaticky použít souběžné řízení.
Obě tyto metody však vracejí počet řádků, které byly ovlivněny operací; to může být zvláště užitečné, pokud chcete sami implementovat řízení souběžnosti.
// (load the ID and concurrency token for a Blog in the database)
var numUpdated = await context.Blogs
.Where(b => b.Id == id && b.ConcurrencyToken == concurrencyToken)
.ExecuteUpdateAsync(/* ... */);
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ě
ExecuteUpdateto nepodporuje aniExecuteDelete. - Více vyvolání těchto metod nelze dávkovat. Každé vyvolání provádí vlastní dotaz do databáze.
- 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
- Komunitní setkání o přístupu k datům .NET, kde probereme
ExecuteUpdateaExecuteDelete.