Accessing data via NHibernate persistence

Component: NHibernate Persistence
NuGet Package NServiceBus.NHibernate (6.x)
Target NServiceBus Version: 5.x

NHibernate persistence supports a mechanism that allows using the same data context used by NServiceBus internals to also store business data. This ensures atomicity of changes done across multiple handlers and sagas involved in processing of the same message. See accessing data to learn more about other ways of accessing the data in the handlers.

The NHibernateStorageContext can be used directly to access NHibernate ISession.

If different connections strings were used for particular persistence features such as sagas, timeouts, etc. then dataContext.Session() will expose connection string for sagas.

Using in a Handler

public class OrderHandler :
    IHandleMessages<OrderMessage>
{
    NHibernateStorageContext dataContext;

    public OrderHandler(NHibernateStorageContext dataContext)
    {
        this.dataContext = dataContext;
    }

    public void Handle(OrderMessage message)
    {
        var nhibernateSession = dataContext.Session;
        nhibernateSession.Save(new Order());
    }
}

Using in a Saga

public class OrderSaga :
    Saga<OrderSaga.SagaData>,
    IHandleMessages<OrderMessage>
{
    NHibernateStorageContext dataContext;

    public OrderSaga(NHibernateStorageContext dataContext)
    {
        this.dataContext = dataContext;
    }

    public void Handle(OrderMessage message)
    {
        var nhibernateSession = dataContext.Session;
        nhibernateSession.Save(new Order());
    }
Other than interacting with its own internal state, a saga should not access a database, call out to web services, or access other resources. See Accessing databases and other resources from a saga.

If the situation is special enough to warrant going against this recommendation, the following documentation will describe how to do so.

ISession instances can also be injected directly into the handlers or sagas but it requires explicit configuration.

var persistence = busConfiguration.UsePersistence<NHibernatePersistence>();
persistence.RegisterManagedSessionInTheContainer();
public class OrderHandler :
    IHandleMessages<OrderMessage>
{
    ISession session;

    public OrderHandler(ISession session)
    {
        this.session = session;
    }

    public void Handle(OrderMessage message)
    {
        session.Save(new Order());
    }
}

The first part instructs that the ISession instance should be injected into the handlers and the second part uses constructor injection to access the ISession object.

Regardless of how the ISession object is accessed, it is fully managed by NServiceBus according to the best practices defined by NServiceBus documentation with regards to transactions.

Customizing the session

Version 6 supports customizing the instantiation of the ISession. This is done by hooking up to the creation process by providing a custom delegate:

var persistence = busConfiguration.UsePersistence<NHibernatePersistence>();
persistence.UseCustomSessionCreationMethod(
    callback: (sessionFactory, connectionString) =>
    {
        return sessionFactory.OpenSession();
    });
Customizing the way session is opened works only for the 'shared' session that is used to access business/user, Saga and Outbox data. It does not work for other persistence concerns such as Timeouts or Subscriptions. Also note that this is no longer possible in Version 7.

Known limitations

When the transport transaction mode is set to TransactionScope, NServiceBus opens a session by passing an existing instance of a database connection. Therefore, it is not possible to use NHibernate's second-level cache. The reason why sessions are opened this way in NServiceBus is because of an unresolved bug in NHibernate.

In other transport transaction modes the session is created using a parameterless OpenSession() and an explicit NHibernate transaction is used which makes it possible to take advantage of the second-level cache.

TransactionScope mode

When the transport is set to TransactionScope transaction mode NServiceBus expects the persistence to hook into the ambient transaction in order to ensure exactly-once message processing. The persistence does it by ensuring that the database connection created for synchronized storage session is enlisted in the transports transaction by calling DbConnection.EnlistTransaction method.

If a database driver configured for the persistence does not support this method (e.g. MySQL), an exception will be thrown to prevent incorrect business behavior (executing handler in at-least-once mode when user expects exactly-once). To mitigate this problem

  • use database that supports distributed transactions (such as SQL Server or Oracle) or
  • enable Outbox or
  • rewrite message handler logic to ensure it is idempotent.

Related Articles


Last modified