В чем разница между.ToList(), .AsEnumerable(), AsQueryable()?
Я знаю некоторые отличия LINQ to Entities и LINQ to Objects, которые реализует первый IQueryable
а второй реализует IEnumerable
и мой вопрос находится в пределах EF 5.
Мой вопрос: в чем техническая разница (и) этих 3 методов? Я вижу, что во многих ситуациях все они работают. Я также вижу, используя их комбинации, как .ToList().AsQueryable()
,
Что конкретно означают эти методы?
Есть ли проблемы с производительностью или что-то, что могло бы привести к использованию одного над другим?
Зачем использовать, например,
.ToList().AsQueryable()
вместо.AsQueryable()
?
5 ответов
Об этом можно многое сказать. Позвольте мне сосредоточиться на 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
против ToList
в том, что AsEnumerable
не выполняет запрос. AsEnumerable
сохраняет отложенное выполнение и не создает часто бесполезный промежуточный список.
С другой стороны, когда требуется принудительное выполнение запроса LINQ, ToList
может быть способ сделать это.
AsQueryable
может использоваться для того, чтобы перечисляемая коллекция принимала выражения в операторах LINQ. Смотрите здесь для более подробной информации: действительно ли мне нужно использовать AsQueryable() на коллекции?,
Обратите внимание на токсикоманию!
AsEnumerable
работает как наркотик. Это быстрое решение, но оно стоит денег и не решает основную проблему.
Во многих ответах на Stack Overflow я вижу людей, подающих заявку 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
производит 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
поэтому обычные методы расширения не применяются.
К списку()
- Выполнить запрос немедленно
AsEnumerable()
- ленивый (выполнить запрос позже)
- Параметр:
Func<TSource, bool>
- Загрузите КАЖДУЮ запись в память приложения, а затем обработайте / отфильтруйте их. (Например, где /Take/Skip, он выберет * из таблицы1 в память, затем выберет первые X элементов) (В этом случае, что он сделал: Linq-to-SQL + Linq-to-Object)
AsQueryable ()
- ленивый (выполнить запрос позже)
- Параметр:
Expression<Func<TSource, bool>>
- Конвертируйте Expression в T-SQL (с конкретным поставщиком), выполняйте удаленный запрос и загружайте результат в память вашего приложения.
- Вот почему DbSet (в Entity Framework) также наследует IQueryable для получения эффективного запроса.
- Не загружайте каждую запись, например, если Take(5), он сгенерирует топ 5 * SQL в фоновом режиме. Это означает, что этот тип более дружественен к базе данных SQL, и поэтому этот тип обычно имеет более высокую производительность и рекомендуется при работе с базой данных.
- Так
AsQueryable()
обычно работает намного быстрее чемAsEnumerable()
сначала он генерирует T-SQL, который включает все ваши условия where в вашем Linq.
ToList() будет все в памяти, а затем вы будете работать над этим. Итак, ToList(). где (применить некоторый фильтр) выполняется локально. AsQueryable() выполнит все удаленно, т.е. фильтр по нему будет отправлен в базу данных для применения. Queryable ничего не делает, пока вы не выполните его. ToList, однако, выполняется немедленно.
Кроме того, посмотрите на этот ответ. Зачем использовать AsQueryable () вместо List ()?,
РЕДАКТИРОВАТЬ: Кроме того, в вашем случае, когда вы делаете ToList(), то каждая последующая операция является локальной, включая AsQueryable (). Вы не можете переключиться на удаленный, когда вы начнете выполнять локально. Надеюсь, это сделает это немного яснее.
Обнаружена плохая производительность в приведенном ниже коде.
void DoSomething<T>(IEnumerable<T> objects){
var single = objects.First(); //load everything into memory before .First()
...
}
Исправлено с
void DoSomething<T>(IEnumerable<T> objects){
T single;
if (objects is IQueryable<T>)
single = objects.AsQueryable().First(); // SELECT TOP (1) ... is used
else
single = objects.First();
}
Для IQuerya ble оставайтесь в IQueryable, когда это возможно, старайтесь не использовать его как IEnumerable.
Обновление Это можно еще больше упростить в одном выражении, спасибо Gert Arnold.
T single = objects is IQueryable<T> q?
q.First():
objects.First();
// Main use of AsQueryable operator is in unit testing to mock a queryable data
//source using an in memroy data source
public class Employee
{
public int Id { set; get; }
public string Name { set; get; }
public string Gender { set; get; }
public int Salary { set; get; }
}
public class Program
{
public static void Main()
{
List<Employee> employees = new List<Employee>
{
new Employee{Id=101,Name="Tom",Gender="Male",Salary = 800},
new Employee{Id=102,Name="Marry",Gender="Female",Salary = 900},
new Employee{Id=103,Name="Pam",Gender="Female",Salary = 800},
new Employee{Id=104,Name="Jhon",Gender="Male",Salary = 800},
new Employee{Id=105,Name="Jhon",Gender="Male",Salary = 800},
new Employee{Id=106,Name="Brin",Gender="Male",Salary = 700},
new Employee{Id=107,Name="Jade",Gender="Male",Salary = 750},
new Employee{Id=108,Name="Ron",Gender="Male",Salary = 850},
new Employee{Id=109,Name="Rob",Gender="Male",Salary = 950},
new Employee{Id=110,Name="Alex",Gender="Male",Salary = 750},
new Employee{Id=111,Name="Susan",Gender="Female",Salary = 860}
};
//this is in-memory data source but assume that we have retrived list of employee from data base
var result = employees
.Where(x => x.Gender == "Male") //<-- All conditions applied on SQL server side
.OrderByDescending(x => x.Salary)
.Take(5);
var result1 = employees
.AsEnumerable()
.Where(x => x.Gender == "Male") //<-- Get all Data From SQL server and then apply all condtions in memory i.e Where,OrderByDescending &Take
.OrderByDescending(x => x.Salary)
.Take(5);
var result2 = employees
.Where(x => x.Gender == "Male") //<-- Get filtered Data (only male employee) from SQL server and then apply OrderByDescending &Take
.AsEnumerable()
.OrderByDescending(x => x.Salary)
.Take(5);
//1. Create a table in sql server
//2. Create Linq To SQL mapping
//3. Retrive data using Linq To SQL
//4. run sql profiler(Event selection uncheck all audit events) and execute above query one by one you will how data retrived from sql server
//So in short, use AsEnumerable operator to move query processing to the client side
}