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

Какая разница между возвращением 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 ответов

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Есть блог с кратким примером исходного кода о том, как злоупотреблять 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 источник.

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

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

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

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

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

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

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

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

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

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

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

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

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

(б) передать IQueryable для представления, чья модель IEnumerable, (Действительно. IQueryable продолжается 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,

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

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

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

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

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

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

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

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

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

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

Что выбрать?

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

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

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

разница между возвращением IQueryable <T> и IEnumerable <T и IEnumerable

В дополнение к вышесказанному, интересно отметить, что вы можете получить исключения, если вы используете IQueryable вместо IEnumerable:

Следующее работает нормально, если products является IEnumerable:

products.Skip(-4);

Однако если products является IQueryable и он пытается получить доступ к записям из таблицы БД, тогда вы получите эту ошибку:

Смещение, указанное в предложении OFFSET, не может быть отрицательным.

Это потому, что был создан следующий запрос:

SELECT [p].[ProductId]
FROM [Products] AS [p]
ORDER BY (SELECT 1)
OFFSET @__p_0 ROWS

и OFFSET не может иметь отрицательное значение.

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

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

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

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

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

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

Другие вопросы по тегам