tl;dr Множество Include
s взривява набора от SQL резултати. Скоро става по-евтино да се зареждат данни чрез множество извиквания на база данни, вместо да се изпълнява един мега оператор. Опитайте се да намерите най-добрата комбинация от Include
и Load
изявления.
изглежда, че има намаление на производителността при използване на Include
Това е подценяване! Множество Include
s бързо взривява резултата от SQL заявката както по ширина, така и по дължина. Защо е така?
Коефициент на растеж на Include
с
(Тази част важи Entity Framework classic, v6 и по-стари)
Да кажем, че имаме
- основен обект
Root
- родителски обект
Root.Parent
- дъщерни обекти
Root.Children1
иRoot.Children2
- изявление LINQ
Root.Include("Parent").Include("Children1").Include("Children2")
Това създава SQL израз, който има следната структура:
SELECT *, <PseudoColumns>
FROM Root
JOIN Parent
JOIN Children1
UNION
SELECT *, <PseudoColumns>
FROM Root
JOIN Parent
JOIN Children2
Тези <PseudoColumns>
се състоят от изрази като CAST(NULL AS int) AS [C2],
и те служат да имат еднакво количество колони във всички UNION
-ed заявки. Първата част добавя псевдо колони за Child2
, втората част добавя псевдо колони за Child1
.
Ето какво означава това за размера на SQL резултатния набор:
- Брой колони в
SELECT
клаузата е сборът от всички колони в четирите таблици - Броят на редовете е сборът от записи във включените дъщерни колекции
Тъй като общият брой точки от данни е columns * rows
, всеки допълнителен Include
експоненциално увеличава общия брой точки от данни в резултатния набор. Позволете ми да демонстрирам това, като взема Root
отново, сега с допълнителен Children3
колекция. Ако всички таблици имат 5 колони и 100 реда, получаваме:
Едно Include
(Root
+ 1 дъщерна колекция):10 колони * 100 реда =1000 точки от данни.
Две Include
s (Root
+ 2 дъщерни колекции):15 колони * 200 реда =3000 точки от данни.
Три Include
s (Root
+ 3 дъщерни колекции):20 колони * 300 реда =6000 точки от данни.
С 12 Includes
това би възлизало на 78 000 точки данни!
Обратно, ако получите всички записи за всяка таблица поотделно вместо 12 Includes
, имате 13 * 5 * 100
точки от данни:6500, по-малко от 10%!
Сега тези числа са донякъде преувеличени, тъй като много от тези точки от данни ще бъдат null
, така че те не допринасят много за действителния размер на набора от резултати, който се изпраща на клиента. Но размерът на заявката и задачата за оптимизатора на заявки със сигурност ще бъдат засегнати отрицателно от увеличаването на броя на Include
с.
Баланс
Така че използвайки Includes
е деликатен баланс между цената на обажданията към базата данни и обема на данните. Трудно е да се даде някакво правило, но досега можете да си представите, че обемът на данните обикновено бързо надвишава разходите за допълнителни обаждания, ако има повече от ~3 Includes
за дъщерни колекции (но доста повече за родителски Includes
). , което само разширява набора от резултати).
Алтернатива
Алтернативата на Include
е зареждане на данни в отделни заявки:
context.Configuration.LazyLoadingEnabled = false;
var rootId = 1;
context.Children1.Where(c => c.RootId == rootId).Load();
context.Children2.Where(c => c.RootId == rootId).Load();
return context.Roots.Find(rootId);
Това зарежда всички необходими данни в кеша на контекста. По време на този процес EF изпълнява оправяне на връзката чрез който автоматично попълва свойствата за навигация (Root.Children
и т.н.) от заредени обекти. Крайният резултат е идентичен с израза с Include
s, с изключение на една важна разлика:дъщерните колекции не са маркирани като заредени в мениджъра на състоянието на обекта, така че EF ще се опита да задейства мързеливо зареждане, ако имате достъп до тях. Ето защо е важно да изключите мързеливото зареждане.
В действителност ще трябва да разберете коя комбинация от Include
и Load
твърденията работят най-добре за вас.
Други аспекти, които трябва да имате предвид
Всеки Includes
също така увеличава сложността на заявката, така че оптимизаторът на заявки на базата данни ще трябва да полага все повече усилия, за да намери най-добрия план за заявка. В един момент това може вече да не успее. Също така, когато липсват някои жизненоважни индекси (особено на външни ключове), производителността може да пострада чрез добавяне на Include
s, дори и с най-добрия план за заявка.
Ядро на Entity Framework
Картезианска експлозия
По някаква причина описаното по-горе поведение, UNIONed заявки, беше изоставено от EF ядро 3. Сега изгражда една заявка с обединявания. Когато заявката е с форма на звезда, това води до декартова експлозия (в SQL резултатния набор). Мога да намеря само бележка, обявяваща тази критична промяна, но не казва защо.
Разделени заявки
За да се противопостави на тази декартова експлозия, ядрото на Entity Framework 5 въведе концепцията за разделени заявки, която позволява зареждане на свързани данни в множество заявки. Той предотвратява изграждането на един масивен, умножен SQL набор от резултати. Освен това, поради по-ниската сложност на заявката, тя може да намали времето, необходимо за извличане на данни, дори при множество двупосочни пътувания. Това обаче може да доведе до непоследователни данни, когато се появят едновременни актуализации.
Множество връзки 1:n извън корена на заявката.