HangfireIO / HangfireIO/Hangfire
Pending local transaction is not assigned to SQL commands
- Dominant language
- C#
- Stars
- 10.1k
- Forks
- 1.8k
- Avg merge
- 1h 19m
- Merged PRs (30d)
- 1
Description
I am trying to use Hangfire with an existing connection and transaction. Ultimate goal is to save changes together with one or more background jobs in a single transaction.
This fails because the transaction is not set on the SQL commands executed by Hangfire.
I assume this would work on the full framework because one can use a TransactionScope and the SQL commands would auto enlist in the ambient transaction? This is not an option on .NET Core though.
## Exception details
```
Hangfire.BackgroundJobClientException: Background job creation failed. See inner exception for details. ---> System.InvalidOperationException: ExecuteScalar requires the command to have a transaction when the connection assigned to the command is in a pending local transaction. The Transaction property of the command has not been initialized.
at System.Data.SqlClient.SqlCommand.ValidateCommand(Boolean async, String method)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean asyncWrite, String method)
at System.Data.SqlClient.SqlCommand.ExecuteScalar()
at Dapper.SqlMapper.ExecuteScalarImpl[T](IDbConnection cnn, CommandDefinition& command)
at Dapper.SqlMapper.ExecuteScalar[T](IDbConnection cnn, String sql, Object param, IDbTransaction transaction, Nullable`1 commandTimeout, Nullable`1 commandType)
at Hangfire.SqlServer.SqlServerConnection.<>c__DisplayClass7_0.b__0(DbConnection connection)
at Hangfire.SqlServer.SqlServerStorage.UseConnection[T](DbConnection dedicatedConnection, Func`2 func)
at Hangfire.SqlServer.SqlServerConnection.CreateExpiredJob(Job job, IDictionary`2 parameters, DateTime createdAt, TimeSpan expireIn)
at Hangfire.Client.CoreBackgroundJobFactory.Create(CreateContext context)
at Hangfire.Client.BackgroundJobFactory.<>c__DisplayClass7_0.b__0()
at Hangfire.Client.BackgroundJobFactory.InvokeClientFilter(IClientFilter filter, CreatingContext preContext, Func`1 continuation)
at Hangfire.Client.BackgroundJobFactory.Create(CreateContext context)
at Hangfire.BackgroundJobClient.Create(Job job, IState state)
--- End of inner exception stack trace ---
at Hangfire.BackgroundJobClient.Create(Job job, IState state)
```
## Steps to reproduce
```
using (var context = new TestDbContext())
{
context.Database.Migrate();
context.InitializeHangfireIfNecessary();
context.SaveChanges();
}
```
```
public class TestDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=.;Database=HangfireTest;Integrated Security=SSPI");
//optionsBuilder.ConfigureWarnings(x => x.Ignore(RelationalEventId.AmbientTransactionWarning));
base.OnConfiguring(optionsBuilder);
}
public override int SaveChanges()
{
//TransactionScope is not supported on .NET Core.
//Exceptions can be suppressed, but hangfire commands are not enlisted in the transaction
//using (var tx = new TransactionScope())
using (var tx = Database.BeginTransaction())
{
//Reuse existing connection because we want a single transaction
var storage = new SqlServerStorage(Database.GetDbConnection(),
new SqlServerStorageOptions { PrepareSchemaIfNecessary = false });
var client = new BackgroundJobClient(storage);
client.Enqueue(() => Console.WriteLine("Fire-and-forget!"));
var result = base.SaveChanges();
tx.Commit();
return result;
}
}
public void InitializeHangfireIfNecessary()
{
// Initialize Hangfire storage
var storage = new SqlServerStorage(Database.GetDbConnection());
}
}
```
Contributor guide
Research direction
Start at Hangfire.SqlServer.SqlServerConnection.CreateExpiredJob and SqlServerStorage.UseConnection, following the Dapper SQL command shown in the exception. Reproduce the sample with DbContext.Database.BeginTransaction and verify that creating the job no longer fails with a pending local transaction and that the job and saved changes commit together.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- database
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100