Как происходит конвертация LINQ выражений в SQL запросы в Entity Framework Core?
Что проверяют
Понимаете ли вы разницу между IQueryable и IEnumerable и в какой момент запрос реально уходит в базу. Следом обычно спрашивают, что будет, если в Where вызвать свой C#-метод, и как посмотреть сгенерированный SQL.
Как отвечать
Разложите путь запроса по шагам:
- дерево выражений: методы
Queryable.Where,Selectи другие принимаютExpression<Func<...>>, поэтому компилятор C# превращает лямбду не в делегат, а в дерево выражений. Это данные, которые можно разобрать, а не код для исполнения; - отложенное выполнение: пока вы строите цепочку, к базе никто не обращается. Запрос уходит при перечислении:
ToListAsync(),FirstOrDefaultAsync(),CountAsync(),foreach; - трансляция: EF Core раскрывает навигационные свойства в join, переводит дерево в своё реляционное представление и отдаёт его генератору SQL конкретного провайдера (SQL Server, Npgsql). Поэтому один и тот же LINQ даёт разный SQL на разных базах;
- параметры и кеш: захваченные переменные становятся параметрами SQL, а скомпилированный запрос кешируется по форме дерева. Повторный вызов с другим значением заново не транслируется;
- клиентское вычисление: с EF Core 3.0 оно разрешено только в финальной проекции
Select. Если непереводимый метод стоит вWhereилиOrderBy, получитеInvalidOperationExceptionо том, что выражение не удалось перевести.
Покажите, как это контролировать: ToQueryString() возвращает SQL без выполнения, LogTo или логгер категории Microsoft.EntityFrameworkCore.Database.Command пишет всё, что ушло в базу. Для функций базы есть EF.Functions.Like, для Include по нескольким коллекциям AsSplitQuery(), для чтения без изменений AsNoTracking().
Где спотыкаются
Вызывают AsEnumerable() или ToList() в середине цепочки. Всё, что после, выполняется в памяти, и фильтр по таблице в миллион строк считается уже в приложении.
Думают, что EF генерирует SELECT *. На деле он перечисляет колонки, а проекция через Select в DTO забирает только нужные.
Называют ленивую загрузку оптимизацией. Чаще она источник проблемы N+1: цикл по заказам тихо делает отдельный запрос за каждым клиентом.