1

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();
5earch
  • 289
  • 2
  • 4
  • 15
  • Can you provide code showing how you bind IJobFilterProvider and MyDbContext ? Have you tried the non async version of your Begin and Commit transaction ? – jbl Oct 24 '19 at 09:05
  • I think the problem comes from the fact that your Filter is instantiated once and for all with its unique MyContext. This may help https://stackoverflow.com/a/57396553/1236044 you need to write an IFilterProvider which will provide a different filter with its own MyContext on each job. Passing the ServiceProvider to the constructor of the FilterProvider, and using it to instantiate a filter upon GetFIlters should do the trick – jbl Oct 24 '19 at 16:25
  • Thanks, I will take a look at that and report back. – 5earch Oct 25 '19 at 14:30
  • I assume you should not use the client provider (see .UseFilter(provider.GetRequiredService())). Instead you probably should use the Activator to get your server DbContext. – ADM-IT Dec 28 '19 at 18:17

0 Answers0