В чем разница между .ToList (), .AsEnumerable (), AsQueryable ()?
Я знаю некоторые отличия LINQ к объектам и LINQ к объектам, которые первый реализует IQueryable
а второй реализует IEnumerable
и мой вопрос в рамках EF 5.
Мой вопрос в том, какова техническая разница этих трех методов? Я вижу, что во многих ситуациях все они работают. Я также вижу использование их комбинаций как .ToList().AsQueryable()
.
-
Что именно означают эти методы?
- Как создать дерево выражений LINQ, чтобы выбрать анонимный тип
- Включить внуков в EF Query
- LINQ to Entities не распознает метод 'System.String Format (System.String, System.Object, System.Object)'
- Метод не может быть переведен в выражение хранилища
- C # - код для заказа по свойству с использованием имени свойства в виде строки
-
Есть ли какие-либо проблемы с производительностью или что-то, что может привести к использованию одного над другим?
-
Зачем использовать, например,
.ToList().AsQueryable()
вместо.AsQueryable()
?
- «Лямбда-выражение с телом оператора не может быть преобразовано в дерево выражений»
- Entity Framework 4 Single () vs First () vs FirstOrDefault ()
- LEFT JOIN в LINQ для объектов?
- Преобразование String в Int в EF 4.0
- Возможна ли инъекция через динамический LINQ?
- Лучший способ проверить, существует ли объект в Entity Framework?
- Указанный член типа не поддерживается в LINQ to Entities. Поддерживаются только инициализаторы, сущности и свойства навигации сущности
- Linq int to string
Об этом можно многое сказать. Позвольте мне сосредоточиться на 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 (). Вы не можете переключиться на удаленный, как только вы начнете выполнять локально. Надеюсь, что это становится немного более ясным.