Возвращение IEnumerable интерфейс и интерфейс IQueryable


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

15 ответов:

да, оба дадут вам отложенное выполнение.

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

на IEnumerable<T> case, это будет 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 on IQueryable, вы получите только количество запрошенных строк; делая это на IEnumerable<T> приведет к тому, что все ваши строки будут загружены в память.

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

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

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

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

например Where(x => x.City == "<City>") и IEnumerable и IQueryable

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

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

зачем беспокоиться с этим выражением дерева вещь? Я просто хочу Where() для фильтрации данных. Основная причина заключается в том, что как EF, так и Linq2SQL ORMs могут конвертировать деревья выражений непосредственно в 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 profiler мы находим команду, равную:

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

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

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

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

в SQL Server profiler мы находим команду, равную:

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

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

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

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

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

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

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

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

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

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

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

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

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

Если вы хотите транспортировать только набор объектов для перечисления, просто возьмите IEnumerable.

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

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

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

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

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

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

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

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

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

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

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

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

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

недавно я столкнулся с проблемой с IEnumrable V. IQueryable. Используемый алгоритм сначала выполнил запрос IQueryable для получения набора результатов. Затем они были переданы в цикл foreach с элементами, созданными как класс EF. Этот класс EF затем использовался в предложении from запроса Linq to Entity, в результате чего результат был IEnumerable. Я довольно новичок в EF и Linq для сущностей, поэтому потребовалось некоторое время, чтобы выяснить, что такое узкое место. Используя Минипрофилирование, я нашел запрос а затем преобразовал все отдельные операции в один запрос IQueryable Linq for Entities. Интерфейс 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 в памяти.

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

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

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

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

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

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

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

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

решение

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

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

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

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

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

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

ссылка ссылке

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

difference between returning IQueryable<T> vs. IEnumerable<T>

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

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

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

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

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

IEnumerable интерфейс находится в системе.Пространство имен коллекций.

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

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

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

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

Что выбрать?

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

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

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