Ответ
В Entity Framework Core запрос строится с помощью LINQ, но выполняется в базе данных только при вызове терминального оператора (terminal operator). До этого момента строится дерево выражений (Expression Tree).
Основные категории терминальных операторов, выполняющих запрос:
-
Материализация в коллекцию: Преобразуют результат запроса в объект в памяти.
ToList(),ToListAsync()ToArray(),ToArrayAsync()ToDictionary(),ToLookup()
-
Получение одного элемента:
First()/FirstAsync(),FirstOrDefault()/FirstOrDefaultAsync()Single()/SingleAsync(),SingleOrDefault()/SingleOrDefaultAsync()Last()/LastAsync(),LastOrDefault()/LastOrDefaultAsync()(может быть неэффективным, если не отсортировано)
-
Агрегатные функции: Выполняют вычисление на стороне БД и возвращают скалярное значение.
Count()/CountAsync(),LongCount()Any()/AnyAsync()All()/AllAsync()Min()/MinAsync(),Max()/MaxAsync()Sum()/SumAsync(),Average()/AverageAsync()
-
Загрузка связанных данных (Eager Loading): Методы
Include()иThenInclude()сами по себе не выполняют запрос, но влияют на его форму. Запрос выполнится при вызове терминального оператора после них.
Пример, иллюстрирующий отложенное выполнение:
// 1. Только строится дерево выражений. Запрос в БД НЕ выполняется.
var query = context.Users
.Where(u => u.IsActive)
.OrderBy(u => u.LastName)
.Select(u => new { u.Id, u.FullName });
// 2. ЗАПРОС ВЫПОЛНЯЕТСЯ ЗДЕСЬ, при вызове ToListAsync().
var activeUsers = await query.ToListAsync();
// 3. Ещё один ОТДЕЛЬНЫЙ запрос выполнится здесь.
var count = await context.Users.CountAsync(u => u.Age > 25);
Важное замечание: Неоднократный вызов терминальных операторов на одном и том же IQueryable без AsNoTracking() может привести к нескольким запросам к БД. Для повторного использования результатов их нужно материализовать (например, в List).