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
- Do not enable DisableTransactionScope when using external/custom queue providers; leave it false (the default) on .NET Framework.
- Route the affected queues through the default SqlServerJobQueueProvider so the SQL-based enqueue path is used instead of the external path.
- Move to the .NET Core / .NET 5+ build, where FEATURE_TRANSACTIONSCOPE is not defined and TransactionScope is already not used, eliminating the conflict.
- 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
- On .NET Framework, never combine DisableTransactionScope = true with MSMQ or custom queue providers.
- Prefer the default SqlServerJobQueueProvider for all queues when DisableTransactionScope is enabled.
- Consider the .NET Core/.NET 5+ build where TransactionScope is not used and the conflict disappears.
- Document which queues are SQL-backed before turning on DisableTransactionScope.
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
- Could not find connection string with name '{nameOrConnectio
- Only 'SqlServerTimeoutJob' type supports transactional ackno
- Attempts value must be equal or greater than zero.
- DelaysInSeconds value must be an array of non-negative numbe
- client
AI-assisted analysis of HangfireIO/Hangfire@c236dd0f93 (2026-08-13).
Data as JSON: /api/errors/e529e0e82269582c.
Report an issue: GitHub.