Об этом можно многое сказать. Позвольте мне сосредоточиться на 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>- часто используется для этой цели. Преимущество использования AsEnumerablevs. 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, поэтому обычные методы расширения не применяются.