I am building a microservice which is supposed to send periodical notifications through various means.
To process the notifications and triggers I intend to use a BackgroundService which will look into database, call appropriate service based on notification type and mark into the database the notification as being sent.
How can I access the database from the background service in a safe way, not having concurrency issues?
Is it enough to inject IServiceProvider and create a scope?
public class MyBackgroundService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
public MyBackgroundService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await DoWorkAsync(stoppingToken);
}
private async Task DoWorkAsync(CancellationToken stoppingToken)
{
using (IServiceScope scope = _serviceProvider.CreateScope())
{
IRepository<Notification> notificationRepository =
scope.ServiceProvider.GetRequiredService<IRepository<Notification>>();
IRepository<NotificationLog> notificationLogRepository =
scope.ServiceProvider.GetRequiredService<IRepository<NotificationLog>>();
IUnitOfWork unitOfWork =
scope.ServiceProvider.GetRequiredService<IUnitOfWork>();
while(true)
{
if(stoppingToken.IsCancellationRequested)
{
return;
}
var list = await notificationRepository.GetAll();
.....................................
await notificationLogRepository.Add(...);
await unitOfWork.Commit();
}
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
await base.StopAsync(stoppingToken);
}
}
CodePudding user response:
Is it enough to inject IServiceProvider and create a scope?
Yes.
While you can create a single scope for your entire background worker's lifetime, you also can create a scope per "invocation" - in this case, you could create a new scope each time the timer goes off. That way, if there's ever a situation where the next one starts before the current one completes, the two invocations will be guaranteed to have different scopes.
If your "timer" is just doing an await Task.Delay
, though, then there's no possibility of overlapping invocations and separate scopes aren't necessary. Some people prefer them anyway, since "invocation" and "scope" conceptually go well together.