I'm looking for a way to globally wrap db calls in a Hangfire background job in a transaction, similar to implementing the IAsyncActionFilter in AspNetCore.
I have tried implementing the IServerFilter but I don't seem to be getting the same instance of the DbContext when I run my query in the job, so the transaction is always null. (Rollback on error is purposely not yet implemented.)
public class DbContextTransactionFilter : IServerFilter
{
private readonly MyContext _dbContext;
public DbContextTransactionFilter(MyContext dbContext)
{
_dbContext = dbContext;
}
public void OnPerforming(PerformingContext filterContext)
{
Task.Run(async () => await _dbContext.BeginTransactionAsync());
}
public void OnPerformed(PerformedContext filterContext)
{
Task.Run(async () => await _dbContext.CommitTransactionAsync());
}
}
What is the proposed way of doing that?
Edit 1:
This is how I implemented the IAsyncActionFilter and I'm looking to do something similar for Hangfire jobs.
public class DbContextTransactionFilter : IAsyncActionFilter
{
private readonly MyContext _dbContext;
public DbContextTransactionFilter(MyContext dbContext)
{
_dbContext = dbContext;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
try
{
await _dbContext.BeginTransactionAsync();
var actionExecuted = await next();
if (actionExecuted.Exception != null && !actionExecuted.ExceptionHandled)
{
_dbContext.RollbackTransaction();
}
else
{
await _dbContext.CommitTransactionAsync();
}
}
catch (Exception)
{
_dbContext.RollbackTransaction();
throw;
}
}
}
Edit 2: This is how I register the db context and the filter in the startup class:
services.AddDbContext<MyContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyDatabase")));
services.AddScoped<DbContextTransactionFilter>();
services.AddHangfire((provider, configuration) => configuration
.UseFilter(provider.GetRequiredService<DbContextTransactionFilter>())
);
services.AddHangfireServer();