Возвращение IEnumerable <T> против IQueryable <T>

960

В чем разница между возвратом IQueryable<T> vs. IEnumerable<T>?

IQueryable<Customer> custs = from c in db.Customers
where c.City == "<City>"
select c;

IEnumerable<Customer> custs = from c in db.Customers
where c.City == "<City>"
select c;

Будет ли выполняться отсроченное выполнение, и когда следует выбирать другое?

Теги:
linq
ienumerable
linq-to-sql
iqueryable

16 ответов

1603
Лучший ответ

Да, оба предоставят вам отложенное выполнение.

Разница в том, что IQueryable<T> - это интерфейс, который позволяет LINQ-to-SQL (LINQ.-to-anything) работать, Поэтому, если вы уточните свой запрос на IQueryable<T>, этот запрос будет выполнен в базе данных, если это возможно.

Для случая IEnumerable<T> это будет LINQ-to-object, что означает, что все объекты, соответствующие исходному запросу, должны быть загружается в память из базы данных.

В коде:

IQueryable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);

Этот код будет выполнять SQL для выбора только золотых клиентов. Следующий код, с другой стороны, выполнит исходный запрос в базе данных, а затем отфильтрует не-золото клиентов в памяти:

IEnumerable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);

Это довольно важное различие, и работа над IQueryable<T> может во многих случаях избавить вас от возврата слишком большого количества строк из базы данных. Другим ярким примером является создание подкачки: если вы используете Take и Skip на IQueryable, вы получите только количество запрошенных строк; выполнение этого на IEnumerable<T> приведет к загрузке всех ваших строк в память.

  • 67
    Я недавно столкнулся с этим, и мне понадобилось время, чтобы понять, почему. Оказывается, тот факт, что я строил свой запрос по частям, используя IEnumerable означает, что 500 000 записей извлекались (медленно!), А затем группировались в памяти. Изменив его на использование IQueryable при создании запроса, группа на была выполнена на сервере, и я получил только полдюжины строк отсчетов, как я и ожидал.
  • 275
    Я просто хотел бы выразить, насколько легко ваш ответ читается. Я прочитал три статьи в Google, прежде чем нашел ваше простое и понятное объяснение различий. Спасибо! +1
Показать ещё 15 комментариев
244

Верхний ответ хорош, но он не упоминает деревья выражений, которые объясняют "как" два интерфейса. В принципе, существуют два идентичных набора LINQ-расширений. Where(), Sum(), Count(), FirstOrDefault() и т.д. все они имеют две версии: одну, которая принимает функции и принимает выражения.

  • Подпись версии IEnumerable: Where(Func<Customer, bool> predicate)

  • Подпись версии IQueryable: Where(Expression<Func<Customer, bool>> predicate)

Возможно, вы использовали оба из них, не понимая этого, потому что оба вызываются с использованием идентичного синтаксиса:

например. Where(x => x.City == "<City>") работает как на IEnumerable, так и на IQueryable

  • При использовании Where() в коллекции IEnumerable компилятор передает скомпилированную функцию в Where()

  • При использовании Where() в коллекции IQueryable компилятор передает дерево выражений Where(). Дерево выражений похоже на систему отражения, но для кода. Компилятор преобразует ваш код в структуру данных, которая описывает, что делает ваш код в формате, который легко усваивается.

Зачем беспокоиться об этом дереве выражений? Я просто хочу Where() фильтровать мои данные. Основная причина заключается в том, что ORM EF и Linq2SQL могут преобразовывать деревья выражений непосредственно в SQL, где ваш код будет выполняться намного быстрее.

О, это звучит как бесплатный рост производительности, следует ли использовать AsQueryable() повсюду в этом случае? Нет, IQueryable полезен, только если базовый поставщик данных может что-то с этим сделать. Преобразование чего-то вроде обычного List в IQueryable не принесет вам никакой пользы.

  • 64
    Когда принятый ответ прочитан и понят, этот обязательный следующий.
  • 9
    ИМО это лучше чем принятый ответ. Тем не менее, я не понимаю одно: IQueryable не дает никакой пользы для обычных объектов, хорошо, но разве это хуже? Потому что, если это просто не дает никаких преимуществ, недостаточно просто выбрать IEnumerable, поэтому идея использования IQueryable повсюду остается в силе.
Показать ещё 1 комментарий
66

Да, оба используют отложенное выполнение. Давайте проиллюстрируем разницу с использованием профилировщика SQL Server....

Когда мы запускаем следующий код:

MarketDevEntities db = new MarketDevEntities();

IEnumerable<WebLog> first = db.WebLogs;
var second = first.Where(c => c.DurationSeconds > 10);
var third = second.Where(c => c.WebLogID > 100);
var result = third.Where(c => c.EmailAddress.Length > 11);

Console.Write(result.First().UserName);

В профилировщике SQL Server мы найдем команду, равную:

"SELECT * FROM [dbo].[WebLog]"

Примерно за 90 секунд для выполнения этого блока кода используется таблица WebLog с 1 миллионом записей.

Итак, все записи таблицы загружаются в память как объекты, а затем с каждым .Where() это будет другой фильтр в памяти против этих объектов.

Когда мы используем IQueryable вместо IEnumerable в приведенном выше примере (вторая строка):

В профилировщике SQL Server мы найдем команду, равную:

"SELECT TOP 1 * FROM [dbo].[WebLog] WHERE [DurationSeconds] > 10 AND [WebLogID] > 100 AND LEN([EmailAddress]) > 11"

Для выполнения этого блока кода примерно через четыре секунды используется IQueryable.

IQueryable имеет свойство Expression, в котором хранится древовидное выражение, которое начинает создаваться, когда мы использовали result в нашем примере (который называется отложенным исполнением), и в конце это выражение будет преобразовано в SQL запрос для запуска в базе данных.

  • 4
    Мне важно прочитать это объяснение рядом с принятым ответом.
  • 5
    Это учит меня при приведении к IEnumerable, базовый IQueryable теряет свой метод расширения IQueryable.
Показать ещё 1 комментарий
52

И то и другое даст вам отсроченное исполнение, да.

Что касается того, что является предпочтительным по сравнению с другим, это зависит от того, каков ваш основной источник данных.

Возврат IEnumerable автоматически заставит среду выполнения использовать LINQ to Objects для запроса вашей коллекции.

Возврат IQueryable (который, между прочим, реализует IEnumerable) обеспечивает дополнительную функциональность для преобразования вашего запроса во что-то, что могло бы работать лучше с базовым источником (LINQ to SQL, LINQ to XML и т.д.).

26

В общих чертах я бы порекомендовал следующее:

  • Верните IQueryable<T> если вы хотите, чтобы разработчик использовал ваш метод для уточнения запроса, который вы возвращаете перед выполнением.

  • Верните IEnumerable если вы хотите перенести набор объектов для перечисления.

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

  • 2
    «Можно перечислить», а не «можно IEnumerable».
23

В общем, вы хотите сохранить исходный статический тип запроса до тех пор, пока это не имеет значения.

По этой причине вы можете определить свою переменную как "var" вместо IQueryable<> или IEnumerable<>, и вы будете знать, что вы не меняете тип.

Если вы начинаете с IQueryable<>, вы обычно хотите сохранить его как IQueryable<>, пока не появится какая-то веская причина для его изменения. Причина этого заключается в том, что вы хотите предоставить процессору запросов как можно больше информации. Например, если вы собираетесь использовать только 10 результатов (вы вызвали Take(10)), то вы хотите, чтобы SQL Server знал об этом, чтобы он мог оптимизировать свои планы запросов и отправлять вам только данные, которые вы будете использовать.

Убедительной причиной изменения типа от IQueryable<> до IEnumerable<> может быть то, что вы вызываете некоторую функцию расширения, что реализация IQueryable<> в вашем конкретном объекте либо не может обрабатывать, либо обрабатывать неэффективно. В этом случае вам может потребоваться преобразовать тип в IEnumerable<> (путем назначения переменной типа IEnumerable<> или с помощью метода расширения AsEnumerable, например), так что функции расширения, которые вы вызываете, в конечном итоге являются теми, в классе Enumerable вместо класса Queryable.

20

Многое было сказано ранее, но вернемся к корням более техническим способом:

  1. IEnumerable - это коллекция объектов в памяти, которую вы можете перечислить - последовательность в памяти, которая позволяет выполнять итерацию (упрощает цикл внутри foreach, хотя вы можете использовать только IEnumerator). Они хранятся в памяти как есть.
  2. IQueryable - это дерево выражений, которое в какой-то момент преобразуется во что-то другое со способностью перечислять окончательный результат. Я думаю, это то, что смущает большинство людей.

Они, очевидно, имеют разные значения.

IQueryable представляет собой дерево выражений (просто запрос), которое будет преобразовано во что-либо еще базовым поставщиком запросов при вызове API-интерфейсов выпуска, например агрегатных функций LINQ (Sum, Count и т.д.) Или ToList [Array, Dictionary,...]. И объекты IQueryable также реализуют IEnumerable, IEnumerable<T> так что если они представляют запрос, результат этого запроса может быть повторен. Это означает, что IQueryable не должен быть только запросом. Правильный термин - это деревья выражения.

Теперь то, как выполняются эти выражения и к чему они обращаются, зависит от так называемых поставщиков запросов (исполнителей выражений мы можем думать о них).

В мире Entity Framework (который является мистическим базовым поставщиком источника данных или поставщиком запросов) выражения IQueryable транслируются в собственные запросы T-SQL. Nhibernate делает подобные вещи с ними. Вы можете написать свой собственный, следуя принципам, довольно хорошо описанным в LINQ: например, создание ссылки на провайдера IQueryable, и вы можете захотеть иметь пользовательский API запросов для службы поставщика вашего магазина продуктов.

Таким образом, в основном, объекты IQueryable создаются все время до тех пор, пока мы явно не освободим их и не IQueryable систему переписать их в SQL или что-либо еще и отправить цепочку выполнения для дальнейшей обработки.

Что касается отложенного выполнения, то это функция LINQ позволяющая удерживать схему дерева выражений в памяти и отправлять ее в выполнение только по требованию, всякий раз, когда определенные API-интерфейсы вызываются для последовательности (тот же Count, ToList и т.д.).

Правильное использование обоих в значительной степени зависит от задач, стоящих перед вами в конкретном случае. Для хорошо известного шаблона репозитория я лично предпочитаю возвращать IList, то есть IEnumerable over Lists (индексаторы и тому подобное). Поэтому я советую использовать IQueryable только в репозиториях и IEnumerable где-либо еще в коде. Не говоря о проблемах с тестируемостью, что IQueryable ломает и разрушает принцип разделения интересов. Если вы вернете выражение из репозиториев, потребители могут поиграть со слоем персистентности, как они пожелают.

Небольшое дополнение к беспорядку :) (из обсуждения в комментариях)) Ни один из них не является объектами в памяти, так как они не являются реальными типами как таковыми, они являются маркерами типа - если вы хотите углубиться в это. Но имеет смысл (и именно поэтому даже MSDN так выразился) думать о IEnumerables как о коллекциях в памяти, тогда как IQueryables как о деревьях выражений. Дело в том, что интерфейс IQueryable наследует интерфейс IEnumerable, так что, если он представляет запрос, результаты этого запроса могут быть перечислены. Перечисление вызывает выполнение дерева выражений, связанного с объектом IQueryable. Таким образом, на самом деле вы не можете вызвать любой член IEnumerable, не имея объекта в памяти. Он попадет туда, если вы это сделаете, в любом случае, если он не пустой. IQueryables - это просто запросы, а не данные.

  • 3
    Комментарий о том, что IEnumerables всегда находятся в памяти, не обязательно верен. Интерфейс IQueryable реализует интерфейс IEnumerable. Из-за этого вы можете передать необработанный IQueryable, представляющий запрос LINQ-to-SQL, прямо в представление, которое ожидает IEnumerable! Вы можете быть удивлены, обнаружив, что ваш контекст данных истек или что у вас возникли проблемы с MARS (несколько активных наборов результатов).
  • 0
    так что, на самом деле, вы не можете вызвать любой член IEnumerable, не имея объекта в памяти. Он попадет туда, если вы это сделаете, в любом случае, если он не пустой. IQueryables - это просто запросы, а не данные. Но я действительно понимаю вашу точку зрения. Я собираюсь добавить комментарий к этому.
Показать ещё 1 комментарий
17

Существует сообщение в блоге с кратким примером исходного кода о том, как неправильное использование IEnumerable<T> может значительно повлиять на производительность запросов LINQ: Entity Framework: IQueryable vs. IEnumerable.

Если мы углубимся и посмотрим на источники, мы увидим, что существуют различные методы расширения для IEnumerable<T>:

// Type: System.Linq.Enumerable
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
public static class Enumerable
{
    public static IEnumerable<TSource> Where<TSource>(
        this IEnumerable<TSource> source, 
        Func<TSource, bool> predicate)
    {
        return (IEnumerable<TSource>) 
            new Enumerable.WhereEnumerableIterator<TSource>(source, predicate);
    }
}

и IQueryable<T>:

// Type: System.Linq.Queryable
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
public static class Queryable
{
    public static IQueryable<TSource> Where<TSource>(
        this IQueryable<TSource> source, 
        Expression<Func<TSource, bool>> predicate)
    {
        return source.Provider.CreateQuery<TSource>(
            Expression.Call(
                null, 
                ((MethodInfo) MethodBase.GetCurrentMethod()).MakeGenericMethod(
                    new Type[] { typeof(TSource) }), 
                    new Expression[] 
                        { source.Expression, Expression.Quote(predicate) }));
    }
}

Первый возвращает перечислимый итератор, а второй создает запрос через поставщика запросов, указанный в IQueryable source.

10

Недавно я столкнулся с проблемой IEnumerable v. IQueryable. Используемый алгоритм сначала выполнял запрос IQueryable для получения набора результатов. Затем они были переданы в цикл foreach с элементами, созданными как класс Entity Framework (EF). Этот класс EF затем использовался в предложении from запроса Linq to Entity, в результате чего результат был IEnumerable.

Я новичок в EF и Linq для Entities, поэтому потребовалось время, чтобы выяснить, в чем заключалось узкое место. Используя MiniProfiling, я нашел запрос, а затем преобразовал все отдельные операции в один IQueryable Linq for Entities. IEnumerable занял 15 секунд, а IQueryable потребовалось 0,5 секунды для выполнения. Было задействовано три таблицы, и после прочтения я считаю, что запрос IEnumerable фактически формировал перекрестный продукт из трех таблиц и фильтровал результаты.

Попробуйте использовать IQueryables в качестве практического правила и профилируйте свою работу, чтобы сделать ваши изменения измеримыми.

  • 0
    причина была в том, что выражения IQueryable преобразуются в собственный SQL в EF и выполняются прямо в БД, в то время как списки IEnumerable являются объектами в памяти. Они извлекаются из БД в какой-то момент, когда вы вызываете агрегатные функции, такие как Count, Sum или любое To ... и впоследствии работаете в памяти. IQueryable s также застревает в памяти после IQueryable одного из этих API, но если нет, вы можете передать выражение в стек слоев и поэкспериментировать с фильтрами до вызова API. Хорошо спроектированный DAL как хорошо спроектированный репозиторий решит такие проблемы;)
9

это некоторые различия между IQueryable<T> и IEnumerable<T>

Изображение 969

  • 1
    Разве это не требует атрибуции?
9

Я хотел бы прояснить некоторые вещи из-за кажущихся противоречивыми ответов (в основном связанных с IEnumerable).

(1) IQueryable расширяет интерфейс IEnumerable. (Вы можете отправить IQueryable в то, что ожидает IEnumerable без ошибок.)

(2) Итерации IQueryable и IEnumerable LINQ пытаются выполнять ленивую загрузку при повторном наборе результатов. (Обратите внимание, что реализация может быть видна в методах расширения интерфейса для каждого типа.)

Другими словами, IEnumerables не являются исключительно "in-memory". IQueryables не всегда выполняются в базе данных. IEnumerable должен загружать вещи в память (после получения, возможно, лениво), поскольку у него нет абстрактного поставщика данных. IQueryables полагаться на абстрактного поставщика (например, LINQ-to-SQL), хотя это также может быть провайдером .NET в памяти.

Пример использования

(a) Получить список записей как IQueryable из контекста EF. (Нет записей в памяти.)

(b) Передайте IQueryable в представление, модель которого IEnumerable. (Действителен. IQueryable extends IEnumerable.)

(c) Итерация и доступ к записям набора данных, дочерним сущностям и свойствам из представления. (Может вызывать исключения!)

Возможные проблемы

(1) Попытка IEnumerable проигрывает ленивую загрузку, а ваш контекст данных истек. Исключение выбрано потому, что поставщик больше не доступен.

(2) Прокси сущностей Entity Framework включены (по умолчанию), и вы пытаетесь получить доступ к связанному (виртуальному) объекту с истекшим контекстом данных. То же, что и (1).

(3) Множество активных наборов результатов (MARS). Если вы выполняете итерацию по IEnumerable в блоке foreach( var record in resultSet ) и одновременно пытаетесь получить доступ к record.childEntity.childProperty, вы можете закончиться MARS из-за ленивой загрузки как набора данных, так и реляционного объекта. Это вызовет исключение, если оно не включено в вашей строке подключения.

Решение

  • Я обнаружил, что включение MARS в строке соединения работает нерегулярно. Я предлагаю вам избегать MARS, если он не будет хорошо понят и явно желателен.

Выполните запрос и сохраните результаты, вызвав resultList = resultSet.ToList() Это, по-видимому, самый простой способ обеспечить, чтобы ваши объекты были в памяти.

В случаях, когда вы получаете доступ к связанным объектам, вам может потребоваться контекст данных. Либо это, либо вы можете отключить сущности объекта и явно Include связанные объекты из вашего DbSet.

8

Основное различие между "IEnumerable" и "IQueryable" заключается в том, где выполняется логика фильтра. Один выполняется на стороне клиента (в памяти), а другой - в базе данных.

Например, мы можем рассмотреть пример, в котором у нас есть 10000 записей для пользователя в нашей базе данных и, скажем, только 900 из них являются активными пользователями, поэтому в этом случае, если мы используем "IEnumerable", то сначала он загружает все 10000 записей в память и затем применяет фильтр IsActive, который в итоге возвращает 900 активных пользователей.

С другой стороны, в том же случае, если мы используем "IQueryable", он непосредственно применяет фильтр IsActive к базе данных, который непосредственно оттуда вернет 900 активных пользователей.

Ссылка Ссылка

  • 0
    Какой из них оптимизирован и легкий вес с точки зрения производительности?
  • 0
    @Sam "IQueryable" является более предпочтительным с точки зрения оптимизации и легкого веса.
4

В дополнение к первым двум действительно хорошим ответам (by driis и Jacob):

IEnumerable интерфейс находится в пространстве имен System.Collections.

Объект IEnumerable представляет собой набор данных в памяти и может перемещать данные только вперед. Запрос, представляемый объектом IEnumerable, выполняется немедленно и полностью, поэтому приложение быстро получает данные.

Когда запрос выполняется, IEnumerable загружает все данные, и если нам нужно его отфильтровать, сама фильтрация выполняется на стороне клиента.

Интерфейс IQueryable находится в пространстве имен System.Linq.

Объект IQueryable обеспечивает удаленный доступ к базе данных и позволяет перемещаться по данным в прямом порядке от начала до конца или в обратном порядке. В процессе создания запроса возвращаемый объект является IQueryable, запрос оптимизирован. В результате во время его выполнения потребляется меньше памяти, меньше пропускная способность сети, но в то же время она может обрабатываться несколько медленнее, чем запрос, возвращающий объект IEnumerable.

Что выбрать?

Если вам нужен весь набор возвращенных данных, тогда лучше использовать IEnumerable, который обеспечивает максимальную скорость.

Если вам НЕ нужен весь набор возвращенных данных, но только некоторые отфильтрованные данные, тогда лучше использовать IQueryable.

4

Мы можем использовать оба метода одинаково, и они отличаются только в производительности.

IQueryable выполняет эффективную работу с базой данных. Это означает, что он создает весь запрос select и получает только связанные записи.

Например, мы хотим, чтобы клиенты топ-10, чье имя начиналось с "Nimal. В этом случае запрос выбора будет сгенерирован как select top 10 * from Customer where name like ‘Nimal%’.

Но если бы мы использовали IEnumerable, запрос был бы как select * from Customer where name like ‘Nimal%’, а первая десятка будет отфильтрована на уровне кодирования С# (он получает все записи клиентов из базы данных и передает их на С#).

0

IEnumrable будет хранить данные в памяти

Но в случае Iqueruable он не хранится в памяти

Для более подробной информации вы проверяете с помощью sql profiler

Первый раз ударил ваш запрос с IQueryable и посмотреть, какой запрос выполнить

Тогда попробуй из IEnumrable

  • 2
    Не имеет особого смысла то, что вы говорите здесь. Обе вещи являются интерфейсами, и интерфейсы не предписывают какую-либо реализацию и поэтому ничего не делают . Кроме того, действительно ли этот вопрос нуждался в другом ответе?
-4

При использовании LINQ to Entities важно понимать, когда для использования IEnumerable и IQueryable. Если мы используем IEnumerable, запрос будет выполнен немедленно. Если мы используем IQueryable, выполнение запроса будет отложено до тех пор, пока приложение не запросит перечисление. Теперь давайте посмотрим, что следует учитывать при принятии решения о том, следует ли используйте IQueryable или IEnumerable. Использование IQueryable дает вам возможность создания сложного запроса LINQ с использованием нескольких операторов без выполнения запроса на уровне базы данных. Запрос получает выполняется только тогда, когда окончательный запрос LINQ получает перечисление.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню