Какие методы (терминальные операторы) в Entity Framework Core выполняют запрос к базе данных?

«Какие методы (терминальные операторы) в Entity Framework Core выполняют запрос к базе данных?» — вопрос из категории Entity Framework, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В Entity Framework Core запрос строится с помощью LINQ, но выполняется в базе данных только при вызове терминального оператора (terminal operator). До этого момента строится дерево выражений (Expression Tree).

Основные категории терминальных операторов, выполняющих запрос:

  1. Материализация в коллекцию: Преобразуют результат запроса в объект в памяти.

    • ToList(), ToListAsync()
    • ToArray(), ToArrayAsync()
    • ToDictionary(), ToLookup()
  2. Получение одного элемента:

    • First() / FirstAsync(), FirstOrDefault() / FirstOrDefaultAsync()
    • Single() / SingleAsync(), SingleOrDefault() / SingleOrDefaultAsync()
    • Last() / LastAsync(), LastOrDefault() / LastOrDefaultAsync() (может быть неэффективным, если не отсортировано)
  3. Агрегатные функции: Выполняют вычисление на стороне БД и возвращают скалярное значение.

    • Count() / CountAsync(), LongCount()
    • Any() / AnyAsync()
    • All() / AllAsync()
    • Min() / MinAsync(), Max() / MaxAsync()
    • Sum() / SumAsync(), Average() / AverageAsync()
  4. Загрузка связанных данных (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).