Skip to content
Mogens Heller Grabe edited this page Apr 19, 2017 · 9 revisions

If you intend to do something with a database in a message handler, and you want the handling of the incoming message to function as a "unit of work", then you might want to check out this page :)

What is a unit of work?

You can read Fowler's ubiquitous description if you're interested in the details, but it basically boils down to this: You want to do stuff - either fully, or not at all.

Usually, when your "stuff" is "doing work in a database", you will use that database and its ability to

  • start a transaction before you start doing stuff
  • do stuff as part of the transaction
  • commit the transaction if all goes well
  • roll back the transaction if something fails

These things can of course be implemented in an ad-hoc fashion whenever you need it, but it's usually better implemented by hooking into Rebus in the right places - this is what we'll talk about here.

Hooking into Rebus in the right places

The easiest way to hook into the right places is to include the Rebus.UnitOfWork package and use its extension methods to manage your unit of work.

It is encouraged that whatever constitutes your unit of work is modeled in a class explicitly made for this purpose, and then you use that class to manage whichever resources are required for your particular usage scenario.

After you Install-Package Rebus.UnitOfWork -ProjectName <your-project> you can

Configure.With(...)
    .(...)
    .Options(o => {
        o.EnableUnitOfWork(Create, Commit, Rollback, Cleanup);
    })
    .Start();

where Create, Commit, Rollback, and Cleanup are generic methods that are invoked as one would expect for each message handled. The EnableUnitOfWork method is overloaded, so it comes in a synchronous as well as an asynchronous flavor.

The expected method signatures are such that it is expected that Create accepts an IMessageContext and returns a unit of work instance TUnitOfWork, whose type is then used to infer the signature of Commit, Rollback, and Cleanup, which all receive IMessageContext and TUnifOfWork – this way, your unit of work instance need not implement any interfaces or anything in order to be hooked up with Rebus.

An example

This example code is taken from the Unit of work sample from the Rebus samples repository – in this case, the unit of work is configured like this:

Configure.With(new CastleWindsorContainerAdapter(container))
    .Transport(t => t.UseMsmq("uow.test"))
    .Options(o =>
    {
        o.EnableUnitOfWork(Create, commitAction: Commit, cleanupAction: Dispose);
    })
    .Start();

where Create looks like this:

static UnitOfWork Create(IMessageContext context)
{
    var unitOfWork = new UnitOfWork();

    // stash current unit of work in the transaction context's items
    context.TransactionContext.Items["uow"] = unitOfWork;

    return unitOfWork;
}

and the Commit and Dispose methods are implemented as these simple expression-bodied members:

static void Commit(IMessageContext context, UnitOfWork uow) => uow.Commit();

static void Dispose(IMessageContext context, UnitOfWork uow) => uow.Dispose();

As you can see, the Create method stashes the created unit of work instance under the uow key in the transaction context, because then it can be retrieved in a factory method configured in the IoC container and then used to resolve the necessary SqlConnection and SqlTransaction instances in our handlers.

And example on how to do this can be seen in the GetUnitOfWork which is invoked as part of the container's handler activation:

static UnitOfWork GetUnitOfWork()
{
    var transactionContext = AmbientTransactionContext.Current;

    if (transactionContext == null)
    {
        throw new InvalidOperationException("Attempted to get transaction context outside of a message handler!");
    }

    // get unit of work that was stashed in the transaction context
    return transactionContext.GetOrThrow<UnitOfWork>("uow");
}

And that's really all there is to it 😃 take a look at the Unit Of Work sample for running code to play around with.

Clone this wiki locally