question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Support for TransactionScope to include the scheduling in an existing transaction

See original GitHub issue

Is 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:open
  • Created 3 months ago
  • Comments:7 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
lahmacommented, Jun 30, 2023

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.

1reaction
NilsEngelbachcommented, Jun 9, 2023

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 service
  • service = ObjectUtils.InstantiateType<T>(implementationType); requires type with empty constructor
internal sealed class ServiceCollectionSchedulerFactory : StdSchedulerFactory
{
    protected override T InstantiateType<T>(Type? implementationType)
    {
        var service = serviceProvider.GetService<T>(); // Can not resolve scoped services!
        if (service is null)
        {
            service = ObjectUtils.InstantiateType<T>(implementationType); // Requires type with empty constructor!
        }
        return service;
    }
}

Maybe a custom ServiceCollectionSchedulerFactory that creates an Scope similar to the MicrosoftDependencyInjectionJobFactory for JobFactory would help? Or how could this be achieved?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Implementing an Implicit Transaction using ...
When your application completes all the work it wants to perform in a transaction, you should call the TransactionScope.Complete method only ...
Read more >
c# - How to explicitly start a transaction in the flow of ...
Yes, ExecuteSqlRawAsync does not start a separate transaction, but you do not need another transaction, you are already inside a transaction, ...
Read more >
Transaction support • SQL Server Transport
The SQL Server transport supports the following transport transaction modes: Transaction scope (distributed transaction) ...
Read more >
TransactionScope Class (System.Transactions)
Initializes a new instance of the TransactionScope class with the specified timeout value and COM+ interoperability requirements, and sets the specified ...
Read more >
Transaction Propagation :: Spring Framework
When the propagation setting is PROPAGATION_REQUIRED , a logical transaction scope is created for each method upon which the setting is applied.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found