Возвращая IEnumerable <T>против IQueryable <T> - PullRequest
1017 голосов
/ 20 мая 2010

В чем разница между возвращением IQueryable<T> против 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;

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

Ответы [ 15 ]

1680 голосов
/ 20 мая 2010

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

Разница в том, что IQueryable<T> - это интерфейс, который позволяет работать LINQ-to-SQL (LINQ.-to-что-либо). Так что если вы уточните свой запрос по 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> приведет к загрузке всех ваших строк в память.

270 голосов
/ 14 февраля 2015

Верхний ответ хорош, но он не упоминает деревья выражений, которые объясняют «как» эти два интерфейса различаются. По сути, есть два идентичных набора расширений 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 не принесет вам никакой пользы.

73 голосов
/ 08 июня 2014

Да, оба используют отложенное выполнение. Давайте проиллюстрируем разницу, используя профилировщик 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 запрос для запуска на ядре базы данных.

55 голосов
/ 20 мая 2010

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

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

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

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

27 голосов
/ 11 апреля 2012

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

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

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

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

25 голосов
/ 05 июня 2014

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

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

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

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

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

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

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

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

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

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

24 голосов
/ 15 мая 2012

Как правило, вы хотите сохранить исходный статический тип запроса, пока он не будет иметь значения.

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

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

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

18 голосов
/ 24 апреля 2013

В блоге есть краткий пример исходного кода о том, как неправильное использование IEnumerable<T> может существенно повлиять на производительность запросов LINQ: Entity Framework: IQueryable против 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.

11 голосов
/ 13 октября 2016

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

image vs. IEnumerable">

11 голосов
/ 13 июля 2012

Недавно я столкнулся с проблемой 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 в качестве практического правила и профилируйте свою работу, чтобы сделать ваши изменения измеримыми.

...