AddHostedService<T>() đăng ký worker dưới dạng singleton, và host không tạo scope nào cho nó.
Inject thẳng một service scoped như DbContext sẽ ném ngay lúc khởi động:
Cannot consume scoped service 'AppDbContext' from singleton 'IHostedService'(Thông báo này đến từ scope validation, mặc định bật ở môi trường Development.)
Giả sử vượt được kiểm tra đó thì còn tệ hơn: DbContext sẽ sống suốt đời app, change tracker tích luỹ vô hạn, kết nối không được trả về pool, và nó không thread-safe. Đây chính là captive dependency.
Cách đúng: inject IServiceScopeFactory, tạo scope cho mỗi vòng lặp công việc và resolve trong scope đó.
csharp
public class SyncWorker(IServiceScopeFactory scopeFactory) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await using var scope = scopeFactory.CreateAsyncScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await ProcessBatchAsync(db, stoppingToken);
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
}
}
}Scope nằm trong vòng lặp, không nằm ngoài — mỗi lượt xử lý là một unit of work sạch, dispose xong thì trả kết nối.