Яка різниця між .ToList (), .AsEnumerable (), AsQueryable ()?


182

Я знаю деякі відмінності LINQ для сутностей і LINQ до об'єктів, які перший реалізує, IQueryableа другий реалізує, IEnumerableі сфера мого питання знаходиться в EF 5.

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

  1. Що саме означають ці методи?

  2. Чи є питання про продуктивність чи щось таке, що призвело б до використання одного над іншим?

  3. Навіщо використовуватись, наприклад, .ToList().AsQueryable()замість .AsQueryable()?


Відповіді:


354

Про це можна сказати багато. Дозвольте мені зосередитися на AsEnumerableі AsQueryableі Згадка ToList()по шляху.

Що роблять ці методи?

AsEnumerableі AsQueryableлиті або новонавернений IEnumerableабо IQueryable, відповідно. Я кажу, що передайте або перетворите з причиною:

  • Коли вихідний об'єкт уже реалізує цільової інтерфейс, сам вихідний об'єкт повертається , але кинутий на цільової інтерфейс. Іншими словами: тип не змінюється, але тип часу компіляції є.

  • Коли вихідний об'єкт не реалізує цільовий інтерфейс, вихідний об'єкт перетворюється на об'єкт, який реалізує цільовий інтерфейс. Так змінюються і тип, і тип часу компіляції.

Дозвольте мені це показати на деяких прикладах. У мене є цей маленький метод, який повідомляє про тип часу компіляції та фактичний тип об'єкта ( люб’язно надано Джону Скету ):

void ReportTypeProperties<T>(T obj)
{
    Console.WriteLine("Compile-time type: {0}", typeof(T).Name);
    Console.WriteLine("Actual type: {0}", obj.GetType().Name);
}

Спробуємо довільну зв'язок до 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 - який перетворює a IEnumerable<T>в a 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, який фільтрує ( 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) })

(Але для цього потрібно лише трохи більше знань ...)

Що ці методи НЕ роблять?

Відновлення можливостей 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(...)

Те, де умова ніколи не буде переведена в 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, отже, звичайні методи розширення не застосовуються.


Дякуємо за відповідь, чи можете ви поділитися своєю відповіддю на 3-те запитання ОП - 3. Чому один використовує, наприклад, .ToList (). AsQueryable () замість .AsQueryable ()? ?
кгздев

@ikram Я не можу придумати нічого, де це було б корисно. Як я пояснив, подання заявки AsQueryable()часто грунтується на помилковому уявленні. Але я дозволю собі трохи закипіти на задній частині голови і побачити, чи можу я додати ще трохи висвітлення цього питання.
Герт Арнольд

1
Чудова відповідь. Не могли б ви уточнити, що відбувається, якщо IEnumerable, отриманий викликом AsEnumerable () на IQueryable, перераховується кілька разів? Чи буде запит виконуватися кілька разів, або дані, вже завантажені з бази даних, в пам'ять будуть повторно використані?
антонінод

@antoninod Гарна ідея. Зроблено.
Герт Арнольд

46

ToList ()

  • Виконайте запит негайно

AsEnumerable ()

  • ледачий (виконати запит пізніше)
  • Параметр: Func<TSource, bool>
  • Завантажте КОЖНУ запис у пам'ять програми, а потім обробіть / фільтруйте їх. (наприклад, де / взяти / пропустити, він вибере * з таблиці1, в пам'ять, потім вибере перші X елементи) (У цьому випадку, що це робилося: Linq-to-SQL + Linq-to-Object)

AsQueyable ()

  • ледачий (виконати запит пізніше)
  • Параметр: Expression<Func<TSource, bool>>
  • Перетворіть вираз у T-SQL (у конкретного постачальника), віддалено запитуйте та завантажуйте результат у пам’ять програми.
  • Ось чому DbSet (в Entity Framework) також успадковує IQueryable для отримання ефективного запиту.
  • Не завантажуйте кожен запис, наприклад, якщо Take (5), це створить обраний топ 5 * SQL у фоновому режимі. Це означає, що цей тип є більш сприятливим для SQL Database, і саме тому цей тип зазвичай має більш високу продуктивність і рекомендується при роботі з базою даних.
  • Так AsQueryable()зазвичай працює набагато швидше, ніж AsEnumerable()спочатку генерує T-SQL, який включає всі ваші умови, де у вашому Linq.

14

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

Також подивіться на цю відповідь Чому використовувати AsQueryable () замість List ()? .

EDIT: Крім того, у вашому випадку, як тільки ви зробите ToList (), то кожна наступна операція є локальною, включаючи AsQueryable (). Ви не можете переключитися на віддалений раз, коли ви розпочнете виконувати локально. Сподіваюсь, це робить це трохи більш зрозумілим.


2
"AsQueryable () виконає все віддалено" Тільки якщо номер переліку вже є запитом. Інакше це неможливо, і все ще працює локально. Питання має ".... ToList (). AsQueryable ()", яке могло б використати деякі роз'яснення у вашій відповіді, IMO.

2

Натрапив на погану продуктивність нижче коду.

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();

}

Щоб IQueryable залишався в IQueryable, коли це можливо, намагайтеся не використовувати його як IEnumerable.

Оновлення . Це можна додатково спростити одним виразом, завдяки Герту Арнольду .

T single =  objects is IQueryable<T> q? 
                    q.First(): 
                    objects.First();
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.