✅ راهحل
از NET 5. به بعد، EF Core دقیقاً برای همین سناریو یک راهحل درجهیک ارائه داده:
IDbContextFactory<T> 🎉
بهجای تزریق یک DbContext با طول عمر Scoped (که برای کل Request زنده میماند)، یک Factory تزریق میکنیم که هر زمان لازم شد یک DbContext سبک، مستقل و بدون تداخل بسازد.
این یعنی:
هر Task → یک DbContext مستقل → یک Connection مستقل → بدون هیچ Conflict👌
💡نکته:
اگر به DbContextOptions دسترسی داری، حتی میتوانی دستی هم Context بسازی:
using var context = new AppDbContext(options);
🛠 ثبت Factory در Program.cs
ء<IDbContextFactory<AppDbContext به صورت Singleton رجیستر میشود و خود AppDbContext همچنان به صورت Scoped قابل استفاده است
builder.Services.AddDbContextFactory<AppDbContext>(options =>
{
options.UseNpgsql(builder.Configuration.GetConnectionString("db"));
});
⚡️ ءRefactor کردن Dashboard برای اجرای موازی
بهجای اینکه AppDbContext را تزریق کنیم، <IDbContextFactory<AppDbContext را تزریق میکنیم.
در هر Task:
1️⃣ یک DbContext جدید میسازیم
2️⃣ ءQuery را اجرا میکنیم
3️⃣ آن را Dispose میکنیم
بدون هیچ تداخلی 🔥
using Microsoft.EntityFrameworkCore;
public class DashboardService(IDbContextFactory<AppDbContext> contextFactory)
{
public async Task<DashboardDto> GetDashboardAsync(int userId)
{
// 1. اجرای موازی تمام Query ها
var ordersTask = GetOrdersAsync(userId);
var logsTask = GetSystemLogsAsync();
var statsTask = GetUserStatsAsync(userId);
// 2. صبر برای اتمام همه
await Task.WhenAll(ordersTask, logsTask, statsTask);
// 3. برگرداندن نتایج
return new DashboardDto(
await ordersTask,
await logsTask,
await statsTask
);
}
private async Task<List<Order>> GetOrdersAsync(int userId)
{
await using var context = await contextFactory.CreateDbContextAsync();
return await context.Orders
.AsNoTracking()
.Where(o => o.UserId == userId)
.OrderByDescending(o => o.CreatedAt)
.ThenByDescending(o => o.Amount)
.Take(50)
.ToListAsync();
}
private async Task<List<SystemLog>> GetSystemLogsAsync()
{
await using var context = await contextFactory.CreateDbContextAsync();
return await context.SystemLogs
.AsNoTracking()
.OrderByDescending(l => l.Timestamp)
.Take(50)
.ToListAsync();
}
private async Task<UserStats?> GetUserStatsAsync(int userId)
{
await using var context = await contextFactory.CreateDbContextAsync();
return await context.Users
.Where(u => u.Id == userId)
.Select(u => new UserStats { OrderCount = u.Orders.Count })
.FirstOrDefaultAsync();
}
}
🧠 مفاهیم کلیدی
1️⃣ جداسازی کامل Contextها 🧩
هر Task → یک DbContext مستقل
یعنی:
• یک Connection جدا
• بدون رقابت روی Change Tracker
• بدون Race Condition
• بدون Exception خطرناک EF Core
2️⃣ ءDispose شدن سریع Context 🧹
با await using، Context فوراً Dispose میشود و Connection به Connection Pool برمیگردد.
این نکته حیاتی است، مخصوصاً زیر بار بالا.