EFCore 6. Как работает метод Select

Есть какая-то таблица, у которой есть FK к другой таблице. Данный метод возвращает статистику игрока. С помощью ключа я нахожу игрока, а затем беру его статистику(другая таблица).

public Statistic GetStatisticFromPlayer(int playerId)
{
      //return _context.Players.FirstOrDefault(p => p.Id == playerId).Statistic; не будет работать, ибо statistic == null
      return _context.Players.Where(p => p.Id == playerId).Select(p => p.Statistic).FirstOrDefault(); 
 }

Первый вариант будет возвращать null , ибо у данного объекта не будет проинициализировано поле Statistic(другая таблица), а с помощью метода Select объект Statistic вернется.

Объясните, пожалуйста, как под капотом работает метод Select.


Ответы (1 шт):

Автор решения: Alexander Petrov

Этот запрос:

_context.Players.FirstOrDefault(p => p.Id == playerId).Statistic;

работает следующим образом. Метод FirstOrDefault является заключительным в цепочке вызовов. После его выполнения из БД будет получен Player. А уже на клиенте будет взято свойство Statistic.

SQL-запрос будет выглядеть так:

SELECT TOP(1) -- колонки Players
FROM Players p
WHERE p.Id = @playerId

Как мы видим, запрос не содержит информации из таблицы Statistic.

По умолчанию EF Core не выполнляет загрузку данных из связанных таблиц.
Можно осуществить эту загрузку тем или иным образом: Loading Related Data.
Самый распространённый способ - Eager loading - энергичная загрузка.
Добавим в запрос Include:

_context.Players.Include(p => p.Statistic).FirstOrDefault(p => p.Id == playerId).Statistic;

Это приведёт к генерации такого запроса:

SELECT TOP(1) -- колонки Players и колонки Statistic
FROM Players AS p
INNER JOIN Statistic AS s ON p.Id = s.PlayerId
WHERE p.Id = @playerId

Будет произведено объединение (JOIN) двух таблиц и получены данные из обеих таблиц.

Это даст желаемый результат: значение свойства Statistic будет содержать объект.
Однако, если кроме этого объекта другие данные (объект Player) не нужны, то этот запрос получается неэффективным: он запрашивает и материализует лишние данные.

Этот ваш запрос:

_context.Players.Where(p => p.Id == playerId).Select(p => p.Statistic).FirstOrDefault();

генерирует следующий SQL:

SELECT TOP(1) -- колонки Statistic
FROM Players AS p
INNER JOIN Statistic AS s ON p.Id = s.PlayerId
WHERE p.Id = @playerId

Как можно видеть, тут из БД возвращаются данные только Statistic. Если данные Player не нужны, то этот запрос эффективнее предыдущего.


SQL-запросы, которые уходят в БД можно смотреть разными способами. Вероятно, самый простой - использовать Simple Logging.

В методе OnConfiguring добавляем строку:

optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);

Параметр LogLevel.Information отсечёт ненужную служебную информацию. Без него будет слишком много шума.


Я исходил из предположения, что используется Sql Server, хотя это не важно.
Сгенерированные sql-запросы я вручную очистил от лишних скобок и прочего.

→ Ссылка