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