Hrozba jménem Client Evaluation v Entity Framework Core
Článek se vztahuje k verzi produktu EF Core 1.0.0
Tento článek byl napsán v roce 2018. Vývojářské technologie se neustále inovují a článek již nemusí popisovat aktuální stav technologie, ideální řešení a můj současný pohled na dané téma.
Jedna ze zajímavých funkcí, které přináší EF Core je tzv. Client vs. Server evaluation. Krom toho, že funkce je velmi užitečná a určitě najde své uplatnění, její nepochopení a neuvážené použítí zcela určitě způsobí v mnoha aplikacích výkonnostní potíže. Pokud jim chcete předejít, přečtěte si tento článek, kde riziko objasním.
👨🎓 Nové školení EF Core pro rok 2020
Školení nejnovější verze Entity Framework Core přímo u Vás ve firmě. Naučíte se používat EF Core v celém životním cyklu aplikace od vytvoření modelu, změn v podobě migrací až po dotazování a profilování databázových dotazů.Více o školení Entity Framework Core
Pracujete-li delší dobu s Entity Framework 6.x, zajisté jste se setkali s chybou ve smyslu:
LINQ to Entities does not recognize the method 'xxx' and this method cannot be translated into a store expression.
Chyba upozorňuje na snahu předat SQL Providerovi nesplnitelný úkol v podobě překladu LINQ dotazu na dotaz databázového úložiště. Většina vývojářů při prvním setkání s touto chybou dlouze bádá, ale časem pochopí příčinu a dokáže se této chybě vyhnout. Příkladem může být dotaz:
var products = db.Products .OrderBy(x => x.Price) .Select(x => new { x.Title, x.SeoLink, Price = UpdatePrice(x.Price) }) .Take(2).ToList();
Nejenže s Entity Framework Core už nevyhučíte na výjimce, ale dokonce dostanete správné a očekávané výsledky.
Client vs. Server Evaluation
DB Provider nově dokáže vyhodnocovat část LINQ dotazu na straně databázového serveru a část na straně klienta. Příklad uvedený nahoře by byl přeložen do klasického SELECT dotazu (upraveno pro čitelnost):
SELECT TOP 2 [x].[Title], [x].[SeoLink], [x].[Price] FROM [Products] AS [x] ORDER BY [x].[Price]
Z SQL dotazu je patrné, že SQL server vrátí všechna potřebná data na klienta a zbytek, tedy provedení metody UpdatePrice() už se děje na klientovi s využitím vlastnosti Price. Vše vypadá jednoduše, ale musíme mít napaměti, že SQL Server vyhodnotí a vrátí data tak, aby s nimi klientská strana mohla dále pracovat. A tady si lze snadno naletět.
V příkladu výše probíhá řazení dle "surové" ceny, což bez problémů dokáže SQL Server. Proto provede řazení, vrátí 2 záznamy a na klientovi se pouze vlastnost Price nastaví na základě výstupu metody UpdatePrice().
Stačí ale drobný přehmat a vše je jinak:
var products = db.Products .Select(x => new { x.Title, x.SeoLink, Price = UpdatePrice(x.Price) }) .OrderBy(x => x.Price) .Take(2).ToList();
Drobná změna v podobě přesunu OrderBy() již generuje o poznání nebezpečnější dotaz, ve kterém už není omezení na první 2 záznamy:
SELECT [x].[Title], [x].[SeoLink], [x].[Price] FROM [Products] AS [x]
V tomto případě totiž chceme provést řazení až na základě toho, co vzejde z metody UpdatePrice(). Databázový server logicky nemůže takové řazení provést a proto SQL Provider zažádá zcela o všechna data ze serveru, aby nad nimi mohla být provedena metoda UpdatePrice() a teprve následně Take().
Stejné chování by nastalo, pokud by vývojář například použil Where(), v jehož podmínce by bylo něco, co databázový server nedokáže vyhodnotit.
Na první pohled vypadá všechno logicky (a logické to je), nicméně když neznalý vývojář přijde k existujícímu kódu s úkolem nastavit řazení záznamů a v dobré víře vrazí OrderBy() na konec dotazu (což je u EF 6.x standardní praktika), vytvoří v případě tabulek s větším objemem dat výkonnostní problém.
Zakázání Client Evaluation
Máte-li z potenciální hrozby strach, můžete tuto feature zakázat a dotazy sestavovat starým způsobem. Opět bude na vás, abyste se rozhodli co dáte za úkol SQL Serveru a co si přepočítáte na klientské straně ve své aplikaci. Funkci je možné zakázat v DbContextu v metodě OnConfiguring():
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { ....... optionsBuilder.ConfigureWarnings(w => w.Throw(RelationalEventId.QueryClientEvaluationWarning)); base.OnConfiguring(optionsBuilder); }
Jakmile se pokusí kdokoliv o Client Evaluation, dojde k vyhození výjimky:
Pokud samozřejmě pracujete v menším týmu vývojářů a máte toto chování neustále na mysli, pak nemá smysl funkci vypínat a můžete ji ke své radosti využívat. V případě aplikací, kde si nelze podobné chyby dovolit nebo ve větších týmech s juniornějšími členy bych doporučil jít raději tradiční cestou.
Aktualizace k verzi 3.x
Ve verzi EF Core 3.0 / 3.1 se funkcionalita Client Evaluation vypnula. Vyhodnocení na straně klienta probíhá obvykle pouze na úrovni poslední projekce. V ostatních případech EF Core automaticky vyhodí výjimku. Hrozba je tedy definitivně zažehnána.