Zrádná metoda ToDictionary a rozhraní IQueryable
Tento článek byl napsán v roce 2015. 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.
Pokud s pomocí Entity Framework často selectujete data z databáze do dictionaries, pravděpodobně pro tento účel používáte extension metodu ToDictionary. Je to ale s ohledem na výkonnost správné? O tom, že se bude jednat o výkonnostní risk se můžeme přesvědčit už z kódu této extension metody. Všimněte si, že metoda pracuje s IEnumerable.
[__DynamicallyInvokable] public static Dictionary<TKey, TElement> ToDictionary<TSource, TKey, TElement>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector) { return Enumerable.ToDictionary<TSource, TKey, TElement>(source, keySelector, elementSelector, (IEqualityComparer<TKey>) null); }
Začněme tedy tím, že:
using (var db = new BookStoreContext()) { Dictionary<int, string> books = context.Books.ToDictionary(x => x.BookId, x => x.Title); }
je výkonnostně naprosto špatně. Výsledný dotaz totiž vypadá například takto:
SELECT [Extent1].[BookId] AS [BookId], [Extent1].[CategoryId] AS [CategoryId], [Extent1].[Title] AS [Title], [Extent1].[Added] AS [Added], [Extent1].[Url] AS [Url] FROM [dbo].[Books] AS [Extent1]
Klasický SELECT * FROM
pak s rostoucím množstvím atributů a případných joinů způsobí dotázání a přenos enormního množství dat. Jednoduché řešení je provést před voláním extension metody projekci:
context.Books.Select(x => new {x.BookId, x.Title}).ToDictionary(x => x.BookId, x => x.Title)
Tím už dostaneme chtěný dotaz:
SELECT [Extent1].[BookId] AS [BookId], [Extent1].[Title] AS [Title] FROM [dbo].[Books] AS [Extent1]
Pokud data načítáme do dictionaries, s největší pravděpodobností se bude jednat o read-only kolekce. Z výkonnostnho hlediska pak nic nebrání provést poslední krok optimalizace:
context.Books.AsNoTracking().Select(x => new {x.BookId, x.Title}).ToDictionary(x => x.BookId, x => x.Title)
Extension metoda AsNoTracking()
vypne trackování změn pro aktuální DbQuery a výrazně ušetří kontextu od správy dat. Dodávám, že výkonnostní rozdíl mezi prvním a posledním příkladem se na časech projevuje i ve dvou řádech.