This post describes how to achieve database transaction, on a command level, with Autofac DI and MediatR.
Sometimes there is a need to share transaction between two different ORMs which uses the same database - in my case Entity Framework and Dapper. It will allow you to be sure that changes, made by two different tools, will be done completely or not. To realize such scenario you can use TransactionScope class. It provides an option to create a transaction, which will be used by SQL connection.
First, you need to create new pipeline behavior - an action which embraces every command. It is a mechanism very well defined in MediatR documentation.
public class TransactionBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var transactionOptions = new TransactionOptions
{
IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted,
Timeout = TransactionManager.MaximumTimeout
};
using (var transaction = new TransactionScope(TransactionScopeOption.Required, transactionOptions,
TransactionScopeAsyncFlowOption.Enabled))
{
// handle request handler
var response = await next();
// complete database transaction
transaction.Complete();
return response;
}
}
}
The behavior creates transaction every executed action and inside this transaction runs a command handler. These posts (1, 2) describe why, by default, you should create **TransactionScope **with above options.
Then you need to register your filter during the creation of Autofac **ContainerBuilder **- typically when the application is being started.
var builder = new ContainerBuilder();
..
builder.RegisterGeneric(typeof(TransactionBehavior<,>))
.As(typeof(IPipelineBehavior<,>));
..
MediatR checks if there are any registered pipeline behaviors and applies them to all incoming commands.
PS. Here you find how to achieve database transaction on a request level.
Comments:
Autofac + MediatR – Shared transaction between ORMs on a command level | Radek Maziarka Blog
Dziękujemy za dodanie artykułu - Trackback z dotnetomaniak.pl
You forget about TransactionScopeAsyncFlowOption.Enabled. :)
Cool post. I guess we can also pass Isolation level in request. The request would implement ITransactional interface with read only property specifing isolation level. The handler would have simple checking if request implements ITransactional. What do you think?
Thanks Dariusz, I added it with some comment :)
From one perspective sounds reasonable. From other, I am not sure if the command should have any information about infrastructural details. But it is doable :)
I did that in one of my projects and it turned out well. We implemented ITransictional interface in explicit way so it would be only accessible when referencing command of this particular interface and not implementation type. This way all infrastructual is hidden inside implementation :)
In what way are you going to specify trans isolation level in approach you presented? Just curious, maybe I don’t see everything :)
After few thoughts I would rather not add this interface to the command. Instead of it, I would try to embrace MediatR mechanism to add isolation level additionally as a parameter, during publishing it. Maybe it’s an quite over-engineering but it allows you to keep your command not aware of infrastructural details and more flexible to different use-cases.