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 шт):
Этот запрос:
_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-запросы я вручную очистил от лишних скобок и прочего.