Об этом можно многое сказать. Позвольте мне сосредоточиться на 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
может использоваться для того, чтобы перечисляемая коллекция принимала выражения в операторах 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, который filters ( Where
) и projects ( 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) })
(Но это требует немного больше знаний ...)
Что эти методы НЕ делают?
Восстановить IQueryable возможности
Теперь важное предостережение. Когда вы делаете
context.Observations.AsEnumerable()
.AsQueryable()
Вы получите исходный объект, представленный как IQueryable
. (Потому что оба метода только приводят и не конвертируют).
Но когда вы делаете
context.Observations.AsEnumerable().Select(x => x)
.AsQueryable()
какой будет результат?
Select
Производит 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
реализации.
казнить
Оба AsQueryable
и AsEnumerable
не выполняют (или перечисляют ) исходный объект. Они только меняют свой тип или представление. Оба задействованных интерфейса, IQueryable
и IEnumerable
, являются ничем иным, как «перечислением, ожидающим произойти». Они не выполняются до того, как их заставляют, например, как упоминалось выше, путем вызова ToList()
.
Это означает , что выполнение IEnumerable
получить, позвонив AsEnumerable
на IQueryable
объект, будет выполнять лежащий в основе IQueryable
. Последующее выполнение IEnumerable
снова будет выполнять IQueryable
. Который может быть очень дорогим.
Конкретные реализации
Пока что речь шла только о методах расширения Queryable.AsQueryable
и Enumerable.AsEnumerable
. Но, конечно, любой может написать методы экземпляра или методы расширения с одинаковыми именами (и функциями).
На самом деле, типичным примером конкретного AsEnumerable
метода расширения является DataTableExtensions.AsEnumerable
. DataTable
не реализует IQueryable
или IEnumerable
, поэтому обычные методы расширения не применяются.