SQL Server Transport Upgrade Version 2 to 3

Namespace changes

The primary namespace is NServiceBus. Advanced APIs have been moved to NServiceBus.Transport.SqlServer. A using NServiceBus directive should be sufficient to find all basic options. A using NServiceBus.Transport.SqlServer is needed to access these configuration options.

Transactions

The native transaction support has been split into two different levels: ReceiveOnly and SendAtomicWithReceive. Both are supported. SendAtomicWithReceive is equivalent to disabling distributed transactions in NServiceBus Version 5.

3.x NServiceBus.SqlServer
var transport = endpointConfiguration.UseTransport<SqlServerTransport>();
transport.Transactions(TransportTransactionMode.SendsAtomicWithReceive);
2.1 NServiceBus.SqlServer
var transactions = busConfiguration.Transactions();
transactions.DisableDistributedTransactions();

As shown in the above snippet, transaction settings are now handled in the transport level configuration.

For more details and examples refer to Transaction configuration API and Transaction support pages.

Connection factory

The custom connection factory method is now expected to be async and no parameters are passed to it by the framework:

3.x NServiceBus.SqlServer
var transport = endpointConfiguration.UseTransport<SqlServerTransport>();
transport.UseCustomSqlConnectionFactory(
    sqlConnectionFactory: async () =>
{
    var connection = new SqlConnection("SomeConnectionString");
    try
    {
        await connection.OpenAsync()
            .ConfigureAwait(false);

        // perform custom operations

        return connection;
    }
    catch
    {
        connection.Dispose();
        throw;
    }
});
2.1 NServiceBus.SqlServer
var transport = busConfiguration.UseTransport<SqlServerTransport>();
transport.UseCustomSqlConnectionFactory(
    connectionString =>
    {
        var connection = new SqlConnection(connectionString);
        try
        {
            connection.Open();

            // custom operations

            return connection;
        }
        catch
        {
            connection.Dispose();
            throw;
        }
    });

Accessing transport connection

Accessing transport connection can be done in Version 2 by injecting SqlServerStorageContext into a handler. This way the handler can access the data residing in the same database as the queue tables within the message receive transaction managed by the transport.

In Version 3 this API has been removed. The same goal can be achieved in Version 3 by using ambient transaction mode.

3.x NServiceBus.SqlServer
var transport = endpointConfiguration.UseTransport<SqlServerTransport>();
transport.Transactions(TransportTransactionMode.TransactionScope);

The handler needs to open a connection to access the data but, assuming both handler and the transport are configured to use the same connection string, there is no DTC escalation. SQL Server 2008 and later can detect that both connections target the same resource and merges the two transaction into a single lightweight transaction.

The above statement is true only when not using the NHibernate persistence. NHibernate persistence opens its own connection within the context of the ambient transaction. See access business data in context of processing a message with NHibernate persistence.

Multi-schema support

The configuration API for multi-schema support has now changed. The Queue Schema parameter is no longer supported in the config file and the code configuration API.

The schema for the configured endpoint can be specified using DefaultSchema method:

3.x NServiceBus.SqlServer
var transport = endpointConfiguration.UseTransport<SqlServerTransport>();
transport.DefaultSchema("myschema");
2.1 NServiceBus.SqlServer
var transport = busConfiguration.UseTransport<SqlServerTransport>();
transport.DefaultSchema("myschema");

or by defining a custom schema per endpoint or queue:

3.x NServiceBus.SqlServer
var transport = endpointConfiguration.UseTransport<SqlServerTransport>();
transport.UseSchemaForEndpoint(endpointName: "sales", schema: "sender");
transport.UseSchemaForQueue(queueName: "error", schema: "control");
2.1 NServiceBus.SqlServer
var transport = busConfiguration.UseTransport<SqlServerTransport>();
transport.UseSpecificConnectionInformation(
    EndpointConnectionInfo.For("sales")
        .UseSchema("sender"),
    EndpointConnectionInfo.For("error")
        .UseSchema("control")
    );

This enables configuring custom schema both for local endpoint as well as for other endpoints that the configured endpoint communicates with. When using configuration file to specify schemas for other endpoints, their schemas should be placed in the MessageEndpointMappings section and follow endpoint-name@schema-name convention:

3.x NServiceBus.SqlServer
<UnicastBusConfig>
  <MessageEndpointMappings>
    <add Assembly="Billing.Contract"
         Endpoint="billing@schema_name"/>
    <add Assembly="Sales.Contract"
         Endpoint="sales@another_schema_name"/>
  </MessageEndpointMappings>
</UnicastBusConfig>

Multi-instance support

The configuration API for multi-instance support has now changed. Multiple connection strings have to be provided by connection factory method passed to EnableLagacyMultiInstanceMode method.

Note that EnableLagacyMultiInstanceMode method replaces both pull and push modes from Version 2.x.

3.x NServiceBus.SqlServer
var transport = endpointConfiguration.UseTransport<SqlServerTransport>();
transport.EnableLegacyMultiInstanceMode(
    sqlConnectionFactory: async address =>
    {
        string connectionString;
        if (address.Equals("RemoteEndpoint"))
        {
            connectionString = "SomeConnectionString";
        }
        else
        {
            connectionString = "SomeOtherConnectionString";
        }
        var connection = new SqlConnection(connectionString);
        await connection.OpenAsync()
            .ConfigureAwait(false);
        return connection;
    });
2.1 NServiceBus.SqlServer
var transport = busConfiguration.UseTransport<SqlServerTransport>();
// Option 1
transport
    .UseSpecificConnectionInformation(
        EndpointConnectionInfo.For("RemoteEndpoint")
            .UseConnectionString("SomeConnectionString"),
        EndpointConnectionInfo.For("AnotherEndpoint")
            .UseConnectionString("SomeOtherConnectionString"));

// Option 2
transport
    .UseSpecificConnectionInformation(
        connectionInformationProvider: x =>
        {
            if (x == "RemoteEndpoint")
            {
                var connection = ConnectionInfo.Create();
                return connection.UseConnectionString("SomeConnectionString");
            }
            if (x == "AnotherEndpoint")
            {
                var connection = ConnectionInfo.Create();
                return connection.UseConnectionString("SomeOtherConnectionString");
            }
            return null;
        });

Note that multi-instance mode has been deprecated and is not recommended for new projects.

Multi-instance support and Outbox

Version 3 does not support Outbox in multi-instance mode.

Circuit breaker

The method PauseAfterReceiveFailure(TimeSpan) is no longer supported. In Version 3, the pause value is hard-coded at 1 second.

Indexes

Queue tables created by version 2.2.1 or lower require manual creation of a non-clustered index on the [Expires] column. The following SQL statement can be used to create the missing index:

2.2.2 - N NServiceBus.SqlServer
CREATE NONCLUSTERED INDEX [Index_Expires]
ON [schema].[queuename]
(
	[Expires] ASC
)
INCLUDE
(
	[Id],
	[RowVersion]
)

A warning will be logged when a missing index is detected.

Related Articles


Last modified