Какие бывают типы загрузки данных в ORM и в чем их отличие
Что проверяют
Понимаете ли вы, сколько SQL-запросов на самом деле уходит в базу из кода на Entity Framework Core. Следом почти всегда спрашивают про проблему N+1 и как её найти.
Как отвечать
Покажите три стратегии на примере EF Core:
- жадная (eager): связанные данные грузятся вместе с основным запросом через
IncludeиThenInclude. С EF Core 5 можно фильтровать внутри:Include(o => o.Items.Where(i => i.IsActive)); - явная (explicit): сущность уже загружена, связь догружается отдельным запросом по вашей команде:
context.Entry(order).Collection(o => o.Items).LoadAsync()или.Reference(...)для одиночной навигации; - ленивая (lazy): навигация грузится при первом обращении к свойству. В EF Core она выключена по умолчанию, включается пакетом
Microsoft.EntityFrameworkCore.Proxiesи вызовомUseLazyLoadingProxies(), навигационные свойства должны бытьvirtual. Вариант без прокси: внедрениеILazyLoader.
Сильный ответ добавляет четвёртый путь, проекцию через Select в DTO: EF строит один запрос ровно с нужными колонками, и стратегия загрузки навигаций не нужна вовсе. Для чтения без изменений ещё AsNoTracking().
Отдельно про цену жадной загрузки. Несколько Include по коллекциям в одном запросе дают декартов взрыв: число строк становится произведением размеров коллекций. AsSplitQuery() разбивает запрос на несколько, по одному на коллекцию, ценой лишних обращений к базе.
Искать такие проблемы помогает логирование SQL через LogTo или категорию Microsoft.EntityFrameworkCore.Database.Command, профилировщик базы, MiniProfiler.
Где спотыкаются
Включают ленивую загрузку и перебирают заказы в цикле, обращаясь к order.Customer. На сотню заказов получается 101 запрос.
Лечат N+1 тем, что вешают Include на всё подряд, и тянут в память половину базы ради одного поля.
Отдают сущности с ленивыми навигациями прямо из контроллера: сериализатор обходит все свойства, догружает связанный граф, а на циклических ссылках падает. Для API нужен DTO.