В чем разница(ы) между.ToList (),.AsEnumerable (), AsQueryable ()?

Я знаю некоторые различия LINQ для сущностей и LINQ для объектов, которые первый реализует IQueryable и второй реализует IEnumerable и моя область вопроса находится в пределах EF 5.

мой вопрос в том, какова техническая разница(ы) этих 3 методов? Я вижу, что во многих ситуациях все они работают. Я также вижу, используя комбинации из них, как .ToList().AsQueryable().

  1. что именно означают эти методы?

  2. есть ли проблемы с производительностью или что-то, что приведет к использованию одного над другим?

  3. зачем использовать, например,.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 (выполнить запрос позже)
  • : Func<TSource, bool>
  • загрузить каждый запись в память приложения, а затем обработать/фильтровать их. (например, где/Take / Skip, он выберет * из таблицы 1, в память, затем выберите первые X элементов) (в этом случае, что он сделал: Linq-to-SQL + Linq-to-Object)

AsQueryable()

  • lazy (выполнить запрос позже)
  • : Expression<Func<TSource, bool>>
  • преобразование выражения в T-SQL (с конкретным поставщиком), запрос удаленно и загрузить результат в память приложения.
  • вот почему DbSet (в Entity Framework) также наследует IQueryable для получения эффективного запроса.
  • не загружайте каждую запись, например, если Take (5), он будет генерировать select топ 5 * SQL в фоновом режиме. Это означает, что этот тип более удобен для базы данных SQL, и именно поэтому этот тип обычно имеет более высокую производительность и рекомендуется при работе с базой данных.
  • так AsQueryable() обычно работает намного быстрее, чем AsEnumerable() как он генерирует T-SQL сначала, который включает в себя все ваши условия where в вашем Linq.

ToList () будет все в памяти, и тогда вы будете работать над этим. Итак, список().где (применить некоторый фильтр ) выполняется локально. AsQueryable () будет выполнять все удаленно, т. е. фильтр на нем отправляется в базу данных для применения. Queryable ничего не делает, пока вы его не выполните. Однако в список, немедленно выполняет.

кроме того, посмотрите на этот ответ зачем использовать AsQueryable () вместо List ()?.

изменить : Кроме того, в вашем случае когда-то вы do ToList () тогда каждая последующая операция является локальной, включая AsQueryable (). Вы не можете переключиться на remote после запуска локального выполнения. Надеюсь, это немного прояснит ситуацию.