EF берет Forever для создания этого запроса

У меня есть отношение родительского и дочернего таблиц. В репозитории я делаю это:

return (from p in _ctx.Parents  
.Include( "Children" )  
select p).AsQueryable<Parent>();  

Затем в фильтре я хочу отфильтровать родителя по списку дочерних идентификаторов:

IQueryable<Parent> qry;  // from above
List<int> ids;  // huge list (8500)
var filtered =
from p in qry.Where( p => p.Children.Any(c => ids.Contains(c.ChildId)) ) select s;  

Мой список идентификаторов огромен. Это создает простой оператор SQL, который имеет огромный список идентификаторов "в (1,2,3...)", но не требуется заметного времени для запуска самостоятельно. EF, однако, занимает целую минуту, чтобы сгенерировать выражение. Я доказал это, установив точку останова и вызвав:

((ObjectQuery<Parent>)filtered).ToTraceString();

Это занимает все время. Является ли проблема в моей последней формулировке linq? Я не знаю другого способа сделать эквивалент Child.ChildId в (ids). И даже если мое выражение linq плохое, как в мире это должно пройти так долго?

Ответ 1

К сожалению, создание запросов в Linq to Entities довольно тяжело, но я обнаружил, что он обычно экономит время из-за возможности создавать запросы из своих компонентов, прежде чем на самом деле нанести удар по базе данных.

Вероятно, способ, которым они реализуют метод Contains, использует алгоритм, предполагающий, что Contains обычно используется для относительно небольшого набора данных. Согласно моим тестам, количество времени, которое требуется для каждого идентификатора в списке, начинает стремительно расти примерно на 8000.

Таким образом, это может помочь разбить ваш запрос на части. Группируйте их в группы по 1000 или менее и объедините кучу выражений Where.

var idGroups = ids.GroupBy(i => i / 1000);
var q = Parents.Include("Children").AsQueryable();
var newQ = idGroups.Aggregate(q, 
    (s, g) => s.Concat(
                  q.Where(w => w.Children.Any(wi => g.Contains(wi.ChildId)))));

Это значительно ускоряет работу, но может быть недостаточно для ваших целей, и в этом случае вам придется прибегнуть к хранимой процедуре. К сожалению, этот конкретный прецедент просто не вписывается в "поле" ожидаемого поведения платформы Entity Framework. Если ваш список идентификаторов может начинаться как запрос из одного контекста Entity, Entity Framework работал бы нормально.

Ответ 2

Перезапишите запрос в синтаксисе Lambda, и он сократит время на целых 3 секунды (или, по крайней мере, для моего проекта EF).

return _ctx.Parents.Include( "Children" ).AsQueryable<Parent>();  

и

IQueryable<Parent> qry;  // from above
List<int> ids;  // huge list (8500)
var filtered = qry.Where( p => p.Children.Any(c => ids.Contains(c.ChildId)) );