В чем разница(ы) между.ToList (),.AsEnumerable (), AsQueryable ()?
Я знаю некоторые различия LINQ для сущностей и LINQ для объектов, которые первый реализует IQueryable
и второй реализует IEnumerable
и моя область вопроса находится в пределах EF 5.
мой вопрос в том, какова техническая разница(ы) этих 3 методов? Я вижу, что во многих ситуациях все они работают. Я также вижу, используя комбинации из них, как .ToList().AsQueryable()
.
что именно означают эти методы?
есть ли проблемы с производительностью или что-то, что приведет к использованию одного над другим?
зачем использовать, например,
.ToList().AsQueryable()
вместо.AsQueryable()
?
3 ответов
есть много, что сказать об этом. Позвольте мне сосредоточиться на AsEnumerable
и AsQueryable
и уже ToList()
по пути.
что делают эти методы?
AsEnumerable
и AsQueryable
cast или конвертировать в IEnumerable
или IQueryable
, соответственно. Я говорю cast или конвертировать причины:
когда исходный объект уже реализует целевой интерфейс, возвращается сам исходный объект, но cast к целевой интерфейс. Другими словами: тип не изменяется, но тип времени компиляции является.
когда исходный объект не реализует целевой интерфейс, исходный объект является преобразовать в объект, реализующий целевой интерфейс. Таким образом, изменяется как тип, так и тип времени компиляции.
позвольте мне показать это на некоторых примерах. У меня есть этот маленький метод, который сообщает тип времени компиляции и фактический тип объекта (любезность Джон Скит):
void ReportTypeProperties<T>(T obj)
{
Console.WriteLine("Compile-time type: {0}", typeof(T).Name);
Console.WriteLine("Actual type: {0}", obj.GetType().Name);
}
давайте попробуем произвольный linq-to-sql Table<T>
, который реализует 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
вы видите, что сам класс таблицы всегда возвращается, но его представление изменяется.
теперь объект, который реализует 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<T>
коллекция как IQueryable<T>
источник данных."(MSDN).
что толку?
AsEnumerable
часто используется для переключения из любого IQueryable
реализация LINQ to objects (L2O), в основном потому, что первый не поддерживает функции, которые имеет L2O. Более подробную информацию см. каково влияние AsEnumerable () на объект LINQ?.
для например, в запросе Entity Framework мы можем использовать только ограниченное число методов. Поэтому, если, например, нам нужно использовать один из наших собственных методов в запросе, мы обычно пишем что-то вроде
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => MySuperSmartMethod(x))
ToList
– которая преобразует IEnumerable<T>
до List<T>
- часто используется и для этой цели. Преимущество использования AsEnumerable
и ToList
это AsEnumerable
не выполняется запрос. AsEnumerable
сохраняет отложенное выполнение и не создает часто бесполезный промежуточный список.
С другой стороны, когда требуется принудительное выполнение запроса LINQ,ToList
может быть способ сделать это.
AsQueryable
может использоваться для создания перечислимой коллекции, принимающей выражения в операторах 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 })
конечно, он работает, вероятно. Но он вытягивает всю таблицу в память, а затем применяет фильтр и проекции. Ну, большинство людей достаточно умны, чтобы делать 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
. (Потому что оба метода только cast и не конвертировать.)
но когда вы делаете
context.Observations.AsEnumerable().Select(x => x)
.AsQueryable()
какой будет результат?
на Select
производит WhereSelectEnumerableIterator
. Это внутренний класс .Net, который реализует IEnumerable
, не IQueryable
. Таким образом, произошло преобразование в другой тип и последующее AsQueryable
больше не может вернуть исходный источник.
подразумевается, что это использование AsQueryable
is не способ волшебным образом ввести запрос поставщик со своими специфическими особенностями в перечисляемое. Предположим, вы это сделаете
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => x.ToString())
.AsQueryable()
.Where(...)
условие where никогда не будет переведено на SQL. AsEnumerable()
после чего операторы LINQ окончательно отключают соединение с поставщиком запросов entity framework.
я намеренно показываю этот пример, потому что я видел здесь вопросы, где люди, например, пытаются "впрыснуть"Include
возможности в коллекцию по вызову AsQueryable
. Он компилируется и запускается, но он делает ничего, потому что базовый объект не имеет Include
больше реализации.
Конкретных Реализаций
пока речь шла только о Queryable.AsQueryable
и Enumerable.AsEnumerable
методы расширения. Но, конечно, любой может написать методы экземпляра или методы расширения с теми же именами (и функциями).
в самом деле, общий пример конкретного AsEnumerable
метод расширения составляет DataTableExtensions.AsEnumerable
. DataTable
не реализует IQueryable
или IEnumerable
, поэтому обычные методы расширения не применяются.
список()
- выполнить запрос немедленно
AsEnumerable ()
- lazy (выполнить запрос позже) :
- загрузить каждый запись в память приложения, а затем обработать/фильтровать их. (например, где/Take / Skip, он выберет * из таблицы 1, в память, затем выберите первые X элементов) (в этом случае, что он сделал: Linq-to-SQL + Linq-to-Object)
Func<TSource, bool>
AsQueryable()
- lazy (выполнить запрос позже) :
- преобразование выражения в T-SQL (с конкретным поставщиком), запрос удаленно и загрузить результат в память приложения.
- вот почему DbSet (в Entity Framework) также наследует IQueryable для получения эффективного запроса.
- не загружайте каждую запись, например, если Take (5), он будет генерировать select топ 5 * SQL в фоновом режиме. Это означает, что этот тип более удобен для базы данных SQL, и именно поэтому этот тип обычно имеет более высокую производительность и рекомендуется при работе с базой данных.
- так
AsQueryable()
обычно работает намного быстрее, чемAsEnumerable()
как он генерирует T-SQL сначала, который включает в себя все ваши условия where в вашем Linq.
Expression<Func<TSource, bool>>
ToList () будет все в памяти, и тогда вы будете работать над этим. Итак, список().где (применить некоторый фильтр ) выполняется локально. AsQueryable () будет выполнять все удаленно, т. е. фильтр на нем отправляется в базу данных для применения. Queryable ничего не делает, пока вы его не выполните. Однако в список, немедленно выполняет.
кроме того, посмотрите на этот ответ зачем использовать AsQueryable () вместо List ()?.
изменить : Кроме того, в вашем случае когда-то вы do ToList () тогда каждая последующая операция является локальной, включая AsQueryable (). Вы не можете переключиться на remote после запуска локального выполнения. Надеюсь, это немного прояснит ситуацию.