Support for TransactionScope to include the scheduling in an existing transaction
See original GitHub issueIs your feature request related to a problem? Please describe.
I want to schedule jobs within a transaction using the TransactionScope
:
// Not working: Making changes and add job in same transaction
public async Task ExampleMethod()
{
using (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
// Make changes to entities
var entity = _dbContext.ExampleEntities.Add(new ExampleEntity() { Name = new Random().Next(1, 100).ToString() });
await _dbContext.SaveChangesAsync();
// Add job for entity
var scheduler = await _schedulerFactory.GetScheduler();
var job = JobBuilder.Create<ExampleJob>()
.WithIdentity("exampleJob")
.UsingJobData("entity-name", entity.Entity.Name)
.Build();
var trigger = TriggerBuilder.Create()
.WithIdentity("exampleTrigger")
.StartNow()
.Build();
await scheduler.ScheduleJob(job, trigger);
transaction.Complete();
}
}
This gives the following exception:
System.InvalidOperationException: A transaction is already in progress; nested/concurrent transactions aren't supported.
at Npgsql.NpgsqlConnection.BeginTransaction(IsolationLevel level, Boolean async, CancellationToken cancellationToken)
at Npgsql.NpgsqlConnection.BeginTransaction(IsolationLevel level)
at Npgsql.NpgsqlConnection.BeginDbTransaction(IsolationLevel isolationLevel)
at Quartz.Impl.AdoJobStore.JobStoreSupport.GetConnection()
It can be fixed by suppressing the TransactionScope
or removing it, but this is not the wanted behavior because everything including the scheduling schould be done in the transaction:
// This is working - but not same transaction
public async Task ExampleMethodWithWorkaround()
{
using (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
// Make changes to entities
var entity = _dbContext.ExampleEntities.Add(new ExampleEntity() { Name = new Random().Next(1, 100).ToString() });
await _dbContext.SaveChangesAsync();
// Add job for entity
var scheduler = await _schedulerFactory.GetScheduler();
var job = JobBuilder.Create<ExampleJob>()
.WithIdentity("exampleJob")
.UsingJobData("entity-name", entity.Entity.Name)
.Build();
var trigger = TriggerBuilder.Create()
.WithIdentity("exampleTrigger")
.StartNow()
.Build();
using (var suppressedTransactionScope = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled))
{
await scheduler.ScheduleJob(job, trigger);
}
transaction.Complete();
}
}
// This is working - but not same transaction
public async Task ExampleMethodWithoutScope()
{
// Make changes to entities
var entity = _dbContext.ExampleEntities.Add(new ExampleEntity() { Name = new Random().Next(1, 100).ToString() });
await _dbContext.SaveChangesAsync();
// Add job for entity
var scheduler = await _schedulerFactory.GetScheduler();
var job = JobBuilder.Create<ExampleJob>()
.WithIdentity("exampleJob")
.UsingJobData("entity-name", entity.Entity.Name)
.Build();
var trigger = TriggerBuilder.Create()
.WithIdentity("exampleTrigger")
.StartNow()
.Build();
await scheduler.ScheduleJob(job, trigger);
}
The complete example can be found here: https://github.com/NilsEngelbach/quartznet-transactionscope-example/blob/main/Source/TransactionScopeExample/ExampleService.cs
Describe the solution you’d like
I also saw that in the Quartz Java Implementation there is the Option to use the JobStoreCMT
where the transaction is also managed outside of Quartz.
How could this feature be implemented in Quartz.NET? I would also love to contribute the feature, but i don’t really know where to start and there are any special things to consider?
Issue Analytics
- State:
- Created 3 months ago
- Comments:7 (3 by maintainers)
Top GitHub Comments
I tried to improve the configuration story a bit with https://github.com/quartznet/quartznet/pull/2063 so if you try to use the example code with latest release from MyGet feed (guidance in readme):
https://github.com/quartznet/quartznet/blob/133c5a34cb39a38c1060ce98c96794bd75344917/src/Quartz.Examples.AspNetCore/Startup.cs#L196-L199
That might guide you further, you store should now support DI via constructor injection.
So the problem is using a custom
scoped
JobStore
that relies on the Dependency Injection in the constructor:var service = serviceProvider.GetService<T>();
will not resolve the scoped serviceservice = ObjectUtils.InstantiateType<T>(implementationType);
requires type with empty constructorMaybe a custom
ServiceCollectionSchedulerFactory
that creates anScope
similar to theMicrosoftDependencyInjectionJobFactory
forJobFactory
would help? Or how could this be achieved?