Ответ
Entity Framework Core предоставляет три основных стратегии загрузки связанных данных (навигационных свойств). Выбор зависит от сценария использования и требований к производительности.
1. Жадная загрузка (Eager Loading)
Связанные данные загружаются одним запросом вместе с основной сущностью с помощью метода Include (и ThenInclude для цепочки).
var order = context.Orders
.Include(o => o.Customer) // Загружаем связанную сущность Customer
.Include(o => o.Items) // Загружаем коллекцию Items
.ThenInclude(i => i.Product) // Загружаем Product для каждого Item
.FirstOrDefault(o => o.Id == orderId);
Плюсы: Один запрос к БД, предсказуемое время выполнения. Минусы: Может привести к избыточному объему данных (over-fetching) и сложным SQL-запросам с множеством JOIN.
2. Явная загрузка (Explicit Loading)
Сущность загружается сначала без связанных данных. Затем связанные данные подгружаются отдельным запросом по требованию.
var order = context.Orders.Find(orderId);
// Загружаем коллекцию
context.Entry(order)
.Collection(o => o.Items)
.Load();
// Загружаем ссылочное свойство
context.Entry(order)
.Reference(o => o.Customer)
.Load();
Плюсы: Полный контроль над моментом загрузки. Минусы: Множественные запросы к БД (проблема N+1, если делать в цикле).
3. Ленивая загрузка (Lazy Loading)
Связанные данные загружаются автоматически при первом обращении к навигационному свойству. Требует установки пакета Microsoft.EntityFrameworkCore.Proxies и настройки.
// 1. Установка в DbContext:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseLazyLoadingProxies();
// 2. Навигационные свойства должны быть `virtual`:
public class Order
{
public virtual Customer Customer { get; set; }
public virtual ICollection<OrderItem> Items { get; set; }
}
// Использование:
var order = context.Orders.Find(orderId);
var customerName = order.Customer.Name; // Здесь выполнится отдельный запрос к БД
Плюсы: Удобство разработки, код не загроможден Include.
Минусы: Высокий риск непреднамеренных запросов N+1, что сильно снижает производительность. Затрудняет понимание реального количества SQL-запросов.
Рекомендация: Для большинства сценариев жадная загрузка с проекцией (Select) является оптимальным выбором, так как позволяет контролировать форму и объем данных одним запросом.
var orderData = context.Orders
.Where(o => o.Id == orderId)
.Select(o => new // Проекция в DTO
{
o.Id,
CustomerName = o.Customer.Name,
Items = o.Items.Select(i => new { i.Id, i.Product.Name })
}).FirstOrDefault();