Ответ 1
Об этом можно многое сказать. Позвольте мне сосредоточиться на AsEnumerable
и AsQueryable
и упомянуть ToList()
на этом пути.
Что делают эти методы?
AsEnumerable
и AsQueryable
лить или преобразовать в IEnumerable
или IQueryable
, соответственно. Я говорю о том, чтобы отличить или конвертировать по одной причине:
-
Когда исходный объект уже реализует целевой интерфейс, сам исходный объект возвращается, но переходит к целевому интерфейсу. Другими словами: тип не изменяется, но тип времени компиляции.
-
Когда исходный объект не реализует целевой интерфейс, исходный объект преобразуется в объект, реализующий целевой интерфейс. Таким образом изменяются тип и тип времени компиляции.
Позвольте мне показать это с некоторыми примерами. У меня есть этот небольшой метод, который сообщает тип времени компиляции и фактический тип объекта (любезность Jon Skeet):
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 к объектам (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
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 })
Конечно, он работает, наверное. Но он извлекает всю таблицу в память, а затем применяет фильтр и прогнозы. Ну, большинство людей достаточно умны, чтобы сначала выполнить 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()
каков будет результат?
Select
создает a WhereSelectEnumerableIterator
. Это внутренний класс .Net, который реализует 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
и Enumerable.AsEnumerable
. Но, конечно, любой может писать методы экземпляра или методы расширения с одинаковыми именами (и функциями).
Фактически, общим примером конкретного метода расширения AsEnumerable
является DataTableExtensions.AsEnumerable
. DataTable
не реализует IQueryable
или IEnumerable
, поэтому регулярные методы расширения не применяются.