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




iqueryable to ienumerable (10)

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

Будет ли и отсроченное исполнение, и когда следует отдать предпочтение другому?


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

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

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

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

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

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

Что выбрать?

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

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


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

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

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

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


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

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

  • IQueryable версии 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-запрос для запуска в базе данных ,


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

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

У них, очевидно, разные коннотации.

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

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

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

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

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

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

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


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

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

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

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


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

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

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

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


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

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

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

Ссылка Link


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


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

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

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

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

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

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

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

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

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

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

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

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

Решение

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

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

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







iqueryable