← Назад к вопросам
Как в EF Core избежать проблемы N+1 запросов? Что такое Include и ThenInclude?
2.2 Middle🔥 111 комментариев
#Базы данных и SQL
Комментарии (1)
🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Избегание проблемы N+1 запросов в Entity Framework Core
Проблема N+1 запросов — это распространенная антипаттерн производительности в ORM, когда для загрузки связанных данных выполняется один основной запрос и N дополнительных запросов для каждой загруженной сущности. В EF Core эта проблема возникает при ленивой загрузке (Lazy Loading) или неправильном использовании явной загрузки.
Стратегии решения проблемы N+1
1. Жадная загрузка с Include/ThenInclude
// ПЛОХО: N+1 запросов
var authors = context.Authors.ToList();
foreach (var author in authors)
{
var books = author.Books.ToList(); // Отдельный запрос для каждого автора!
}
// ХОРОШО: всего 1 запрос
var authorsWithBooks = context.Authors
.Include(a => a.Books) // Жадная загрузка книг
.ToList();
2. Проекции (Select) для частичной загрузки
// Эффективно: загружаем только нужные данные
var authorData = context.Authors
.Select(a => new
{
a.Id,
a.Name,
BookTitles = a.Books.Select(b => b.Title).ToList()
})
.ToList();
3. Использование Split Queries
// Разделение на несколько запросов (полезно при множественных Include)
var authors = context.Authors
.Include(a => a.Books)
.Include(a => a.Publisher)
.AsSplitQuery() // Разделяет на несколько SQL-запросов
.ToList();
4. Явная загрузка с Load
// Контролируемая загрузка связанных данных
var author = context.Authors.First();
context.Entry(author)
.Collection(a => a.Books)
.Query()
.Where(b => b.IsPublished)
.Load(); // Явная загрузка с фильтрацией
Методы Include и ThenInclude
Include() — основной метод для загрузки связанных данных
// Загрузка одной связанной коллекции
var orders = context.Orders
.Include(o => o.OrderItems)
.ToList();
// Загрузка одиночной связанной сущности
var products = context.Products
.Include(p => p.Category)
.ToList();
ThenInclude() — метод для загрузки цепочек связей
// Многоуровневая загрузка (цепочка связей)
var authors = context.Authors
.Include(a => a.Books) // Первый уровень: книги
.ThenInclude(b => b.Reviews) // Второй уровень: отзывы к книгам
.Include(a => a.Publisher) // Другой путь загрузки
.ToList();
Варианты использования ThenInclude:
// Загрузка через коллекции
var customers = context.Customers
.Include(c => c.Orders)
.ThenInclude(o => o.OrderDetails)
.ThenInclude(od => od.Product)
.ToList();
// Комбинирование разных путей
var products = context.Products
.Include(p => p.Supplier)
.Include(p => p.Category)
.ThenInclude(c => c.ParentCategory)
.ToList();
Продвинутые техники и лучшие практики
Фильтрация и сортировка внутри Include
// EF Core 5.0+: фильтрация и сортировка при загрузке
var blogs = context.Blogs
.Include(b => b.Posts
.Where(p => p.IsPublished)
.OrderBy(p => p.CreatedDate)
.Take(5))
.ToList();
Автоматическое включение глобальных фильтров
// В DbContext.OnModelCreating
modelBuilder.Entity<Blog>()
.HasMany(b => b.Posts)
.WithOne()
.HasForeignKey(p => p.BlogId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Post>()
.HasQueryFilter(p => !p.IsDeleted);
Мониторинг производительности
// Включение логирования для анализа запросов
optionsBuilder.UseSqlServer(connectionString)
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging();
Критические рекомендации
- Всегда проверяйте генерируемый SQL через логирование или профилировщик
- Избегайте глубоких цепочек Include — загружайте только необходимые данные
- Используйте AsNoTracking() при операциях только для чтения:
var authors = context.Authors .Include(a => a.Books) .AsNoTracking() .ToList(); - Ограничивайте количество загружаемых данных с помощью Take/Skip
- Рассмотрите использование чистых SQL-запросов для сложных сценариев
Пример комплексного решения
public async Task<List<AuthorDto>> GetAuthorsWithBooksAsync(int page, int pageSize)
{
return await context.Authors
.Include(a => a.Books
.Where(b => b.PublishedYear >= 2020)
.OrderByDescending(b => b.Rating)
.Take(10))
.ThenInclude(b => b.Publisher)
.Include(a => a.ContactInfo)
.AsNoTracking() // Для операций чтения
.OrderBy(a => a.LastName)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(a => new AuthorDto
{
Id = a.Id,
FullName = $"{a.FirstName} {a.LastName}",
BooksCount = a.Books.Count,
TopBooks = a.Books.Select(b => b.Title).ToList()
})
.ToListAsync();
}
Правильное использование Include/ThenInclude и других стратегий загрузки данных — ключ к созданию производительных приложений на EF Core. Всегда анализируйте сгенерированные запросы и тестируйте производительность при работе с большими объемами данных.