Про це можна сказати багато. Дозвольте мені зосередитися на 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>
- часто використовується і для цієї мети. Перевага використання 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, який фільтрує ( 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
, отже, звичайні методи розширення не застосовуються.