В чем разница между .ToList (), .AsEnumerable (), AsQueryable ()?

Я знаю некоторые отличия LINQ к объектам и LINQ к объектам, которые первый реализует IQueryable а второй реализует IEnumerable и мой вопрос в рамках EF 5.

Мой вопрос в том, какова техническая разница этих трех методов? Я вижу, что во многих ситуациях все они работают. Я также вижу использование их комбинаций как .ToList().AsQueryable() .

  1. Что именно означают эти методы?

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

  3. Зачем использовать, например, .ToList().AsQueryable() вместо .AsQueryable() ?

Об этом можно многое сказать. Позвольте мне сосредоточиться на AsEnumerable и AsQueryable и упомянуть ToList() на этом пути.

Что делают эти методы?

AsEnumerable и AsQueryable преобразовать в IEnumerable или IQueryable соответственно. Я говорю о том, чтобы бросить или конвертировать по какой-то причине:

  • Когда исходный объект уже реализует целевой интерфейс, сам исходный объект возвращается, но преобразуется в целевой интерфейс. Другими словами: тип не изменяется, но тип времени компиляции.

  • Когда исходный объект не реализует целевой интерфейс, исходный объект преобразуется в объект, реализующий целевой интерфейс. Таким образом изменяются тип и тип времени компиляции.

Позвольте мне показать это с некоторыми примерами. У меня есть этот небольшой метод, который сообщает тип времени компиляции и фактический тип объекта ( любезность Jon Skeet ):

 void ReportTypeProperties(T obj) { Console.WriteLine("Compile-time type: {0}", typeof(T).Name); Console.WriteLine("Actual type: {0}", obj.GetType().Name); } 

Попробуем произвольную Table linq-to-sql Table , которая реализует IQueryable :

 ReportTypeProperties(context.Observations); ReportTypeProperties(context.Observations.AsEnumerable()); ReportTypeProperties(context.Observations.AsQueryable()); 

Результат:

 Compile-time type: Table`1 Actual type: Table`1 Compile-time type: IEnumerable`1 Actual type: Table`1 Compile-time type: IQueryable`1 Actual type: Table`1 

Вы видите, что сам class таблицы всегда возвращается, но его представление меняется.

Теперь объект, который реализует IEnumerable , а не IQueryable :

 var ints = new[] { 1, 2 }; ReportTypeProperties(ints); ReportTypeProperties(ints.AsEnumerable()); ReportTypeProperties(ints.AsQueryable()); 

Результаты:

 Compile-time type: Int32[] Actual type: Int32[] Compile-time type: IEnumerable`1 Actual type: Int32[] Compile-time type: IQueryable`1 Actual type: EnumerableQuery`1 

Вот оно. AsQueryable() преобразовал массив в EnumerableQuery , который «представляет собой коллекцию IEnumerable в качестве источника данных IQueryable ». (MSDN).

Какая польза?

AsEnumerable часто используется для перехода от любой реализации IQueryable к LINQ к объектам (L2O), главным образом потому, что первая не поддерживает функции, которые имеет L2O. Дополнительные сведения см. В разделе Что такое эффект AsEnumerable () в объекте LINQ? ,

Например, в запросе Entity Framework мы можем использовать только ограниченное число методов. Поэтому, если, например, нам нужно использовать один из наших собственных методов в запросе, мы обычно пишем что-то вроде

 var query = context.Observations.Select(o => o.Id) .AsEnumerable().Select(x => MySuperSmartMethod(x)) 

ToList – который преобразует IEnumerable в List – часто используется для этой цели. Преимущество использования AsEnumerable vs. ToList заключается в том, что AsEnumerable не выполняет запрос. AsEnumerable сохраняет отложенное выполнение и не создает часто бесполезный промежуточный список.

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

AsQueryable может использоваться, чтобы сделать перечисляемые коллекции accept выражениями в операторах LINQ. См. Здесь для более подробной информации: действительно ли мне нужно использовать AsQueryable () для коллекции? ,

Примечание о злоупотреблении психоактивными веществами!

AsEnumerable работает как наркотик. Это быстрое решение, но по себестоимости, и оно не затрагивает основную проблему.

Во многих ответах «Переполнение стека» я вижу, как люди применяют AsEnumerable для устранения практически любых проблем с неподдерживаемыми методами в выражениях LINQ. Но цена не всегда ясна. Например, если вы это сделаете:

 context.MyLongWideTable // A table with many records and columns .Where(x => x.Type == "type") .Select(x => new { x.Name, x.CreateDate }) 

… все аккуратно переведено в оператор SQL, который фильтрует ( Where ) и проекты ( Select ). То есть, как длина, так и ширина, соответственно, набора результатов SQL уменьшаются.

Теперь предположим, что пользователи хотят видеть только часть даты CreateDate . В Entity Framework вы быстро обнаружите, что …

 .Select(x => new { x.Name, x.CreateDate.Date }) 

… не поддерживается (на момент написания). Ах, к счастью, есть исправление AsEnumerable :

 context.MyLongWideTable.AsEnumerable() .Where(x => x.Type == "type") .Select(x => new { x.Name, x.CreateDate.Date }) 

Конечно, это работает, наверное. Но он извлекает всю таблицу в память, а затем применяет фильтр и outlookы. Ну, большинство людей достаточно умны, чтобы заняться тем, Where сначала:

 context.MyLongWideTable .Where(x => x.Type == "type").AsEnumerable() .Select(x => new { x.Name, x.CreateDate.Date }) 

Но все же сначала берутся все столбцы, а проецирование выполняется в памяти.

Реальное решение:

 context.MyLongWideTable .Where(x => x.Type == "type") .Select(x => new { x.Name, DbFunctions.TruncateTime(x.CreateDate) }) 

(Но для этого требуется немного больше знаний …)

Что эти методы НЕ делают?

Теперь важное предостережение. Когда вы это сделаете

 context.Observations.AsEnumerable() .AsQueryable() 

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

Но когда вы это делаете

 context.Observations.AsEnumerable().Select(x => x) .AsQueryable() 

каков будет результат?

В WhereSelectEnumerableIterator Select создается WhereSelectEnumerableIterator . Это внутренний .Net-class, который реализует IEnumerable , а не IQueryable . Таким образом, произошло преобразование в другой тип, и последующий AsQueryable больше не сможет вернуть исходный источник.

Следствием этого является то, что использование AsQueryable – это не способ магически внедрить поставщик запросов с его конкретными функциями в перечислимый. Предположим, вы

 var query = context.Observations.Select(o => o.Id) .AsEnumerable().Select(x => x.ToString()) .AsQueryable() .Where(...) 

Условие where никогда не будет переведено в SQL. AsEnumerable() за которым следуют инструкции LINQ, окончательно сокращает соединение с провайдером запросов сущностей.

Я преднамеренно показываю этот пример, потому что я видел вопросы здесь, где люди, например, пытаются «внедрить» Include возможности в коллекцию, вызвав AsQueryable . Он компилируется и запускается, но ничего не делает, потому что базовый объект больше не имеет реализации Include .

Конкретные реализации

До сих пор это было Queryable.AsQueryable только с методами Queryable.AsQueryable и Enumerable.AsEnumerable . Но, конечно, любой может писать методы экземпляра или методы расширения с одинаковыми именами (и функциями).

Фактически, общим примером конкретного метода расширения AsEnumerable является DataTableExtensions.AsEnumerable . DataTable не реализует IQueryable или IEnumerable , поэтому обычные методы расширения не применяются.

К списку()

  • Выполните запрос сразу

AsEnumerable ()

  • ленивый (выполнить запрос позже)
  • Параметр: Func
  • Загрузите КАЖДУЮ запись в память приложения, а затем обработайте ее / отфильтруйте. (например, Where / Take / Skip, он выберет * из таблицы1, в память, затем выберите первые X-элементы) (В этом случае, что он сделал: Linq-to-SQL + Linq-to-Object)

AsQueryable ()

  • ленивый (выполнить запрос позже)
  • Параметр: Expression>
  • Преобразовать выражение в T-SQL (с конкретным провайдером), запросить удаленно и загрузить результат в вашу память приложения.
  • Вот почему DbSet (в Entity Framework) также наследует IQueryable для получения эффективного запроса.
  • Не загружайте каждую запись, например, если Take (5), она будет генерировать в верхней части выделенного верхнего 5 * SQL. Это означает, что этот тип более дружелюбен к базе данных SQL, поэтому этот тип обычно имеет более высокую производительность и рекомендуется при работе с базой данных.
  • Таким образом, AsQueryable() обычно работает намного быстрее, чем AsEnumerable() поскольку он сначала генерирует T-SQL, который включает все ваши условия в вашем Linq.

ToList () будет все в памяти, а затем вы будете работать над ним. поэтому, ToList (). где (применять некоторый фильтр) выполняется локально. AsQueryable () будет выполнять все дистанционно, т. Е. Фильтр на нем отправляется в базу данных для применения. Queryable ничего не делает, пока вы его не выполняете. ToList, однако, выполняется немедленно.

Кроме того, посмотрите на этот ответ. Зачем использовать AsQueryable () вместо List ()? ,

EDIT: Кроме того, в вашем случае, когда вы делаете ToList (), каждая последующая операция является локальной, включая AsQueryable (). Вы не можете переключиться на удаленный, как только вы начнете выполнять локально. Надеюсь, что это становится немного более ясным.

  • Тип узла выражения LINQ «ArrayIndex» не поддерживается в LINQ to Entities
  • В LINQ для Entities поддерживаются только конструкторы и инициализаторы без параметров
  • ASP.NET MVC 2.0 Реализация поиска в jqgrid
  • Предложение "NOT IN" в LINQ to Entities
  • Сравнение с регистром LINQ to Entities
  • Значение cast для значения «Int32» не выполнено, поскольку материализованное значение равно null
  • Сколько Include я могу использовать в ObjectSet в EntityFramework для сохранения производительности?
  • «Тип узла выражения LINQ« Invoke »не поддерживается в LINQ to Entities» - тупик!
  • Как сделать массовую вставку - Linq для объектов
  • Entity Framework: запрос дочерних объектов
  • 'Содержит ()' обходной путь с использованием Linq для Entities?
  • Давайте будем гением компьютера.