Transaction Scopes when enabling Outbox

Hello,

We’ve recently went through the process of updating our ~20 NSB Handlers to version 6. With that, we wanted to remove our dependency on DTC so we also made the switch to enable outbox,as well as moved from NHibernate Persistence to SQL Persistence. We noticed an issue though where partial updates began happening.

For example, we handle a message that performs an update to our Application DB, when it’s finished it also publishes another message to be consumed by another handler. What we were seeing is only after enabling outbox, when the handler handles the message, the database transaction would commit immediately before the successful completion of the handler. If the handler failed somewhere else, the messages would not be sent though. My understanding was the entire handler would enlist in the Outbox transaction scope, not just the message sending/publishing.

When turning outbox off, I was able to see that the entire transaction (DB updates and message publishing) were enlisted together and if it failed, everything would be rolled back, as expected.

In order to keep Outbox enabled and wrap our handlers in a transaction again, we enabled UnitOfWork on the endpoint configuration and set WrapHandlersInTransactionScope. I know this isn’t the intended use of this functionality, but it did the trick for now.

Additional Info:

  • The business data and the outbox data are under the same connection string
  • The Transport database is in a separate database with a different connection string

My Questions:

  1. Is the behavior I’m describing above the correct behavior? If so, what is the best way to go about wrapping my handlers in a transaction to avoid partial updates?
  2. If that is not the designed behavior, will our current “fix” of enabling WrapAllHandlersInATransactionScope suffice for now?

I’m happy to provide as much additional information as I can.

1 Like

Hello,

I’m doing a bit of guesswork here, but it sounds like you’re using SqlTransport on one database (queues only) and SqlPersistence (for Sagas, Outbox, Subscriptions) + Business data on another database. I’m also guessing that your handler is just executing SQL stuff against the business data, using a connection string that just happens to match the SqlPersistence connection string. Am I right?

If that’s the case, then without the TransactionScope sitting there to magically enlist any SQL Connection that gets opened, you need to provide the specific SqlConnection/SqlTransaction that SQL Persistence is using in order to get your Outbox+Business data to be persisted together in the same local transaction, which is a requirement for making Outbox work.

That would explain why turning WrapHandlersInTransactionScope on fixes everything (although reintroduces DTC) because suddenly there’s a magic coordinator sitting in there to make sure everything commits together.

If that’s the case, then you need to be getting your SqlConnection from the IMessageHandlerContext as shown in this sample:

If that’s NOT it, then let me know where my assumptions above are incorrect.

Thanks,
David

Hey David,

Thank you for the reply! This got us talking / investigating implementing your solution, which ultimately led us to another question. It looks like this is going to work for us. It’s going to be a little messy updating all of our repositories, but it doesn’t look like it can be avoided if we want to rid ourselves of DTC.

Thanks,
Eric