IQueryable против IEnumerable - PullRequest
       37

IQueryable против IEnumerable

1 голос
/ 10 января 2020

Рассмотрим следующий фрагмент:

IQueryable<Person> pQ = from p in db.People select p;
IEnumerable<Person> pE = pQ;

Console.WriteLine(pQ.Count());
Console.WriteLine(pE.Count());

Оба дают одинаковый результат, но если вы отслеживаете сгенерированную SQL, то версия IQueryable выдает запрос, используя COUNT, тогда как IEnumerable версия просто выдает SELECT, вытягивая все строки и делая подсчет в памяти, что потенциально очень неэффективно. То же самое относится и к другим методам, таким как Sum() и Average(), и происходит как в EF6, так и в EF Core 3.

Причина в том, что Count() является методом расширения и поэтому привязывается к состоянию c тип переменной (IQueryable в первом случае, IEnumerable во втором), а не тип Dynami c (одинаковый в обоих случаях, поскольку это один и тот же объект).

Это это действительно неприятная ошибка, то есть обычно лучше избегать IEnumerable в таких случаях. Однако есть обходной путь:

Console.WriteLine(pE.AsQueryable().Count());

, что означает, что SQL COUNT выпущен.

(Примечание: приведение к IQueryable также будет работать здесь, но не в Вообще - если бы IEnumerable просто ссылался на данные в памяти, тогда приведение не состоялось бы, но AsQueryable() просто окружило бы объект в нейтральной оболочке.)

Мой вопрос такой: так как эта проблема это так просто упустить и может вызвать такое неэффективное поведение, почему вызов AsQueryable() не просто встроен в IEnumerable реализации методов расширения?

1 Ответ

0 голосов
/ 10 января 2020

У меня такой вопрос: поскольку эту проблему так легко упустить из виду и она может вызвать такое неэффективное поведение, почему вызов AsQueryable() не просто встроен в IEnumerable реализации методов расширения?

Причина в том, что не всегда вы хотите вызывать IQueryable методы расширения.

Если тип источника реализует IQueryable<T>, AsQueryable<TElement>(IEnumerable<TElement>) возвращает его напрямую , В противном случае он возвращает IQueryable<T>, который выполняет запросы, вызывая методы эквивалентного оператора запроса в Enumerable вместо методов в Queryable.

В случае, если вы хотите материализовать запрос, вы можете вызовите AsEnumerable метод, который возвращает IEnumerable<T> или используйте присваивание переменной IEnumerable<T> (как вы это сделали), а затем вызов Count метода снова вызовет AsQueryable метод, который вернет оригинальный IQueryable<T> объект и выполнит Count для IQueryable<T>.

Проблема в том, что IQueryable использует QueryProvider, который переводит данное дерево выражений в фактический запрос, и не каждое дерево выражений можно преобразовать в действительный запрос, что может привести к ошибке. Есть много поставщиков запросов, которые были / не были достаточно зрелыми, и в этом случае просто возникали исключения. Который я считаю даже хуже, чем снижение производительности. Также команда Entity Framework Core считает, что, поскольку некоторые запросы частично переведены, а остальные оцениваются локально (к счастью, EFCore не выдает исключений).

Если я могу предложить вам, как решить эту возможную проблему с пропуском, я бы написал roslyn анализатор, который будет исследовать код и искать IEnumerable<T> enumerable = queryable; назначения и предупреждать, если он что-то обнаружил. Возможно, некоторые из них уже реализованы на GitHub, поскольку вы можете быть не единственным, кто им занимается.

...