TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.75K subscribers
Post #3012 2.33K
День 2509. #ЗаметкиНаПолях
DbContext не Является Потокобезопасным. Распараллеливаем Запросы EF Core Правильно. Окончание

Начало

Решение
Начиная с .NET 5, EF Core предоставляет решение для этого сценария: IDbContextFactory<T>. Вместо внедрения экземпляра контекста с ограниченной областью видимости (который существует на протяжении всего HTTP-запроса), вы внедряете фабрику, которая позволяет создавать легковесные, независимые экземпляры DbContext там, где это нужно.

Примечание: хотя использование фабрики является наиболее чистым подходом для внедрения зависимостей, вы также можете вручную создать экземпляр контекста (используя var context = new AppDbContext(options)), если у вас есть доступ к DbContextOptions.

Сначала нужно зарегистрировать фабрику в Program.cs:
// Это регистрирует фабрику как Singleton
// Также регистрирует AppDbContext как Scoped
builder.Services.AddDbContextFactory<AppDbContext>(
opts =>
{
opts.UseNpgsql(
builder.Configuration.GetConnectionString("db"));
});

Теперь изменим конечную точку, используя фабрику контекстов. Внутри нашего метода мы запускаем отдельную задачу для каждого запроса. Внутри каждой задачи создаём отдельный контекст, выполняем запрос, а затем немедленно освобождаем его:
using Microsoft.EntityFrameworkCore;

public class DashboardService(
IDbContextFactory<AppDbContext> ctxFactory)
{
public async Task<DashboardDto>
GetDashboardAsync(int id)
{
var orders = GetOrdersAsync(id);
var logs = GetSystemLogsAsync();
var stats = GetUserStatsAsync(id);

await Task.WhenAll(orders, logs, stats);

return new DashboardDto(
await orders,
await logs,
await stats
);
}

private async Task<List<Order>>
GetOrdersAsync(int id)
{
// Создаём контекст для операции
await using var ctx =
await ctxFactory.CreateDbContextAsync();

return await ctx.Orders
.AsNoTracking()
.Where(o => o.UserId == id)
.ToListAsync();
}
// остальные методы аналогичны
}


Ключевые особенности
1. Изоляция
Каждая задача получает свой собственный DbContext. Т.е. у каждой задачи есть собственное соединение с БД. Конфликтов нет.
2. Освобождение ресурсов
Использование await критически важно. Как только запрос завершится, мы хотим освободить этот контекст и вернуть соединение в пул.

При последовательном выполнении каждая операция ожидает завершения предыдущей. Использование параллельного подхода позволяет ускорить время ответа. Все три этапа обработки данных в БД начинаются и завершаются параллельно.

Компромиссы и заключение
IDbContextFactory устраняет разрыв между архитектурой EF Core, основанной на единице работы, и реальностью современных требований к параллельной обработке. Он позволяет выйти за рамки принципа «один запрос — один поток» без ущерба для безопасности.

Однако используйте этот шаблон с осторожностью:
1. Истощение пула соединений
Один HTTP-запрос теперь одновременно занимает 3 соединения с БД вместо 1. При высокой степени параллелизма вы можете легко исчерпать пул соединений.
2. Накладные расходы на контекст
Если ваши запросы выполняются очень быстро (например, простой поиск по ID), накладные расходы на создание нескольких контекстов и задач могут сделать параллельную версию медленнее, чем последовательную.

В следующий раз, когда вы увидите медленную панель мониторинга, не спешите сразу же использовать чистый SQL. Проверьте ожидаемые задачи. Если они выполняются последовательно, возможно, параллелизация запросов поможет.

Источник: https://www.milanjovanovic.tech/blog/dbcontext-is-not-thread-safe-parallelizing-ef-core-queries-the-right-way
  • 👍 24
More from @netdeveloperdiary
  1. Sep 29, 2026Фото 3 (с) Анатолий Кулаков
  2. Sep 29, 2026День 2799. Конференция DotNext 2026. Часть 1 25 и 26 сентября в Москве прошла очередная ко…
  3. Sep 28, 2026День 2798. #Оффтоп Утиная Типизация в C# с Помощью Перехватчиков. Часть 2 Некоторое время…
  4. Sep 27, 2026День 2797. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Окончание Начало Продол…
  5. Sep 26, 2026День 2796. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Продолжение Начало Три…
  6. Sep 25, 2026День 2795. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Начало Проблема с позиц…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →