Getting Started
Architecture
NServiceBus
Persistence
ServiceInsight
ServicePulse
ServiceControl
Monitoring
Samples

Transaction support

NuGet Package: NServiceBus.Transport.SqlServer (8.x)
Target Version: NServiceBus 9.x

The SQL Server transport supports the following transport transaction modes:

  • Transaction scope (distributed transaction)
  • Transport transaction - Send atomic with receive
  • Transport transaction - receive only
  • Unreliable (transactions disabled)

TransactionScope mode is particularly useful as it enables exactly once message processing with distributed transactions. However, when transport, persistence, and business data are all stored in a single SQL Server catalog, it is possible to achieve exactly-once message delivery without distributed transactions. For more details, refer to the SQL Server native integration sample.

Transaction scope

In this mode, the ambient transaction is started before receiving the message. The transaction encompasses all stages of processing including user data access and saga data access.

If either the configured NServiceBus persistence mechanism or the user data access also supports transactions via TransactionScope, the ambient transaction could be promoted to a distributed transaction.

See also a sample covering this mode of operation using either SQL Persistence or NHibernate Persistence.

Native transactions

In this mode, the message is received inside a native ADO.NET transaction

There are two available options within native transaction level:

  • ReceiveOnly - An input message is received using native transaction. The transaction is committed only when message processing succeeds.
  • SendsAtomicWithReceive - This mode is similar to the ReceiveOnly, but transaction is shared with sending operations. That means the message receive operation and any send or publish operations are committed atomically.

Unreliable (transactions disabled)

In this mode, when a message is received it is immediately removed from the input queue. If processing fails the message is lost because the operation cannot be rolled back. Any other operation that is performed when processing the message is executed without a transaction and cannot be rolled back. This can lead to undesired side effects when message processing fails part way through.

User provided transactions

Native transactions

When sending messages it is possible to provide custom SqlTransaction instance that will be used when executing transport operations. This enables sending two or more messages in a single, atomic transaction or share the same transaction between transport and relational data store.

This API can be used both with MessageSession and in the message receive context eg. in a handler.

using (var connection = new SqlConnection(connectionString))
{
    connection.Open();

    using (var transaction = connection.BeginTransaction())
    {
        var sqlCommand = new SqlCommand(commandText, connection, transaction);

        //Execute SQL statement
        sqlCommand.ExecuteNonQuery();

        //Send a message
        var sendOptions = new SendOptions();
        sendOptions.UseCustomSqlTransaction(transaction);
        await session.Send(new Message(), sendOptions);

        //Publish a message
        var publishOptions = new PublishOptions();
        publishOptions.UseCustomSqlTransaction(transaction);
        await session.Publish(new Event(), publishOptions);

        transaction.Commit();
    }
}

Transaction scope

When sending messages it is possible to provide custom SqlConnection instance that will be used when executing transport operations. This can be useful in scenarios when the connection enlists in a TransactionScope before it's passed to the send operations.

This API can be used both with MessageSession and in the message receive context eg. in a handler.

using (var scope = new TransactionScope(TransactionScopeOption.RequiresNew, 
    TransactionScopeAsyncFlowOption.Enabled))
{
    using (var connection = new SqlConnection(connectionString))
    {
        connection.Open();

        var sqlCommand = new SqlCommand(commandText, connection);

        //Execute SQL statement
        sqlCommand.ExecuteNonQuery();

        //Send a message
        var sendOptions = new SendOptions();
        sendOptions.UseCustomSqlConnection(connection);
        await session.Send(new Message(), sendOptions);

        //Publish a message
        var publishOptions = new PublishOptions();
        publishOptions.UseCustomSqlConnection(connection);
        await session.Publish(new Event(), publishOptions);
    }

    scope.Complete();
}