HangfireIO/Hangfire · error · NotSupportedException

`DisableTransactionScope` option does not support external q

Error message

`DisableTransactionScope` option does not support external queue providers

What it means

NotSupportedException thrown in SqlServerWriteOnlyTransaction.AddToQueue (SqlServerWriteOnlyTransaction.cs:246), compiled only under FEATURE_TRANSACTIONSCOPE (.NET Framework). It fires when the target queue is served by an external/non-default IPersistentJobQueueProvider whose queue type is not SqlServerJobQueue, AND SqlServerStorageOptions.DisableTransactionScope is true. External queue providers rely on TransactionScope for ambient-transaction enlistment; DisableTransactionScope disables that mechanism, so enqueuing through them is unsupported.

Source

Thrown at src/Hangfire.SqlServer/SqlServerWriteOnlyTransaction.cs:246

            var persistentQueue = provider.GetJobQueue();

            if (persistentQueue.GetType() == typeof(SqlServerJobQueue))
            {
                var query = _storage.GetQueryFromTemplate(static schemaName =>
$@"insert into [{schemaName}].JobQueue (JobId, Queue) values (@jobId, @queue)");

                AddCommand(_queueCommands, queue, batch => batch.Create(query)
                    .AddParameter("@jobId", long.Parse(jobId, CultureInfo.InvariantCulture), DbType.Int64)
                    .AddParameter("@queue", queue, DbType.String));

                _queuesToSignal.Add(queue);
            }
            else
            {
#if FEATURE_TRANSACTIONSCOPE
                if (_storage.Options.DisableTransactionScope)
                {
                    throw new NotSupportedException($"`{nameof(SqlServerStorageOptions.DisableTransactionScope)}` option does not support external queue providers");
                }
#endif
                _queueCommandQueue.Enqueue((connection, transaction) => persistentQueue.Enqueue(
                    connection,
#if !FEATURE_TRANSACTIONSCOPE
                    transaction,
#endif
                    queue,
                    jobId));
            }
        }

        public override void IncrementCounter(string key)
        {
            if (key == null) throw new ArgumentNullException(nameof(key));

            var query = _storage.GetQueryFromTemplate(static schemaName =>
$@"insert into [{schemaName}].Counter ([Key], [Value]) values (@key, @value)");

View on GitHub (pinned to c236dd0f93)

Solutions

  1. Do not enable DisableTransactionScope when using external/custom queue providers; leave it false (the default) on .NET Framework.
  2. Route the affected queues through the default SqlServerJobQueueProvider so the SQL-based enqueue path is used instead of the external path.
  3. Move to the .NET Core / .NET 5+ build, where FEATURE_TRANSACTIONSCOPE is not defined and TransactionScope is already not used, eliminating the conflict.
  4. Remove the external queue provider registration if it is no longer needed.

Example fix

// before (.NET Framework, MSMQ provider registered)
var options = new SqlServerStorageOptions { DisableTransactionScope = true };
globalConfig.UseSqlServerStorage(connStr, options);
// queues routed to MSMQ -> throws on AddToQueue
// after
var options = new SqlServerStorageOptions(); // DisableTransactionScope left false
globalConfig.UseSqlServerStorage(connStr, options);
Defensive patterns

Strategy: validation

Validate before calling

// Before startup, on .NET Framework, reject the incompatible combination.
#if FEATURE_TRANSACTIONSCOPE
var usesExternalProvider = storage.QueueProviders.ContainsNonSqlServerProvider();
if (options.DisableTransactionScope && usesExternalProvider)
{
    throw new InvalidOperationException("DisableTransactionScope cannot be used with external queue providers; remove the option or the provider.");
}
#endif

Prevention

When it happens

Trigger: On .NET Framework: set options.DisableTransactionScope = true, register a custom or MSMQ-based queue provider, and enqueue a job to a queue that resolves to that provider. AddToQueue sees persistentQueue.GetType() != typeof(SqlServerJobQueue) and throws.

Common situations: A team enables DisableTransactionScope to fix abandoned locks or connection-pool exhaustion (as the option's doc remarks describe), while still routing some queues through MSMQ or a custom provider; upgrading/migrating from MSMQ queues without removing the option.

Related errors


AI-assisted analysis of HangfireIO/Hangfire@c236dd0f93 (2026-08-13). Data as JSON: /api/errors/e529e0e82269582c. Report an issue: GitHub.