How to make Azure Service Bus client to not participate to the ambient transaction when sending a message

Viewed 627

I understand that DTC's are not supported by Azure Service Bus and if you try to do so you get an exception like this: 'Local transactions are not supported with other resource managers/DTC.'

My problem is that I need to send a message to a service bus and the code might be executed within a transaction scope together with possible DB-operations. But the service bus doesn't need to be particularly part of this transaction; so, DTC is not really needed here. However, the service bus client seems to participate automatically to the ambient transaction which elevates the transaction to a DTC.

Examples:

This runs correctly (service bus code is the only one in the transaction):

        using (var tx = new TransactionScope())
        {
            //A simple Azure Bus operation
            var builder = new ServiceBusConnectionStringBuilder(connectionString);
            var queueClient = new QueueClient(builder);
            var messageBody = new Message(Encoding.UTF8.GetBytes("Hello"));
            messageBody.MessageId = Guid.NewGuid().ToString("N");
            queueClient.SendAsync(messageBody).GetAwaiter().GetResult();                

            tx.Complete();
        }

But from the moment another system participates (here an Sql connection) the "DTC are not supported by Azure Service Bus"-exception is thrown:

        using (var tx = new TransactionScope())
        {
            //A simple DB operation
            SqlConnection sqlConnection = new SqlConnection(dbConnectionString);
            sqlConnection.Open();
            SqlCommand cmd = new SqlCommand("INSERT INTO [dbo].[Table_1]([Name]) values ('Hello')", sqlConnection);
            cmd.ExecuteNonQuery();

            //A simple Azure Bus operation
            var builder = new ServiceBusConnectionStringBuilder(connectionString);
            var queueClient = new QueueClient(builder);
            var messageBody = new Message(Encoding.UTF8.GetBytes("Hello"));
            messageBody.MessageId = Guid.NewGuid().ToString("N");
            queueClient.SendAsync(messageBody).GetAwaiter().GetResult();

            queueClient.CloseAsync().GetAwaiter().GetResult();
            sqlConnection.Close();

            tx.Complete();
        }

This error is understandable and already explained here. But is there a way to tell the service bus client to ignore the ambient transaction?

1 Answers

You will need to suppress the ambient transaction and wrap your Service Bus code with the following:

public async Task Method()
{
  SqlConnection sqlConnection = new SqlConnection(dbConnectionString);
  sqlConnection.Open();

  using (var tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
  {
    SqlCommand cmd = new SqlCommand("INSERT INTO [dbo].[Table_1]([Name]) values 
('Hello')", sqlConnection);
    await cmd.ExecuteNonQueryAsync();

    using (var tx = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled))
    {
       var builder = new ServiceBusConnectionStringBuilder(connectionString);
       var queueClient = new QueueClient(builder);
       var messageBody = new Message(Encoding.UTF8.GetBytes("Hello"));
       messageBody.MessageId = Guid.NewGuid().ToString("N");
       queueClient.SendAsync(messageBody).GetAwaiter().GetResult();

       queueClient.CloseAsync().GetAwaiter().GetResult();

       tx.Complete();
    }

    tx.Complete();
  }
  sqlConnection.Close();
}

Note that

  1. You should not be recreating your queue client each time. Keep it around for performance considerations.
  2. Do not use asynchronous APIs in a synchronous code. Rather convert your method to be async. SQL operations are IO-bound just as Service Bus. It's better to have the method to be asynchronous.
Related