ASP.NET

Autofac + ASP.NET - Shared transaction between ORMs on a request level

2018-01-04
image from Autofac + ASP.NET - Shared transaction between ORMs on a request level

This post describes how to achieve database transaction, on a request level, with Autofac DI and ASP.NET WebApi.

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 an instance of Autofac interface IAutofacActionFilter - filter which is run before and after every HTTP action:

    public class TransactionActionFilter : IAutofacActionFilter
    {
        private TransactionScope _transaction;

        public Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
        {
            if (actionExecutedContext.Exception == null)
            {
                this._transaction.Complete();
            }

            this._transaction.Dispose();
            return Task.CompletedTask;
        }

        public Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
        {
            var transactionOptions = new TransactionOptions
            {
                IsolationLevel = IsolationLevel.ReadCommitted,
                Timeout = TransactionManager.MaximumTimeout
            };
            this._transaction = new TransactionScope(TransactionScopeOption.Required, transactionOptions, 
                                                     TransactionScopeAsyncFlowOption.Enabled);

            return Task.CompletedTask;
        }
    }

The filter creates transaction every executed action and completes it when there is no error. These posts (12) 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.

        protected void Application_Start()
        {
            ..
            var builder = new ContainerBuilder();
            builder.Register(c => new TransactionActionFilter())
                .InstancePerRequest()
                .AsWebApiActionFilterFor<ApiController>();
            ..
        }

The filter is registered with an **InstancePerRequest **option, which forces to create filter only once per request. Without it, for OnActionExecutingAsync and OnActionExecutedAsync, Autofac creates 2 instances of the filter.

In most cases you want to register the filter for all controllers, that’s why its registered against ApiController.But you can implement more sophisticated logic there, even with some reflection included.

PS. Here you find how to achieve database transaction on a command level.


Comments:

dotnetomaniak.pl - Jan 5, 2018

Autofac + ASP.NET – Shared transaction between ORMs on a request level | Radek Maziarka Blog

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

Dariusz Lenartowicz - Jan 6, 2018

Based on my experience I treat this pattern as anti-pattern.

Radek Maziarka - Jan 6, 2018

I understand your point of view, but for different scenarios, different solutions can be applied. It is very well explained in article at .NET Microservices. Architecture for Containerized .NET Applications book - https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/microservice-ddd-cqrs-patterns/domain-events-design-implementation#single-transaction-across-aggregates-versus-eventual-consistency-across-aggregates where they withstand two different sights on that case: Single transaction across aggregates versus eventual consistency across aggregates.

comments powered by Disqus