ORM

Autofac + MediatR - Shared transaction between ORMs on a command level

2018-01-04
image from Autofac + MediatR - Shared transaction between ORMs on a command level

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:

dotnetomaniak.pl - Jan 3, 2018

Autofac + MediatR – Shared transaction between ORMs on a command level | Radek Maziarka Blog

Dziękujemy za dodanie artykułu - Trackback z dotnetomaniak.pl

Dariusz Lenartowicz - Jan 4, 2018

You forget about TransactionScopeAsyncFlowOption.Enabled. :)

Paweł Iżycki - Jan 4, 2018

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?

Radek Maziarka - Jan 5, 2018

Thanks Dariusz, I added it with some comment :)

Radek Maziarka - Jan 5, 2018

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 :)

Paweł Iżycki - Jan 6, 2018

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 :)

Radek Maziarka - Jan 1, 2018

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.

comments powered by Disqus