Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NHibernate with Autofac within ASP.NET (MVC): ITransaction

What is the best approach to managing NHibernate transaction using Autofac within web application?

My approach to session is

builder.Register(c => c.Resolve<ISessionFactory>().OpenSession())
       .ContainerScoped();

For ITransaction, I have found an example on Google Code, but it relies on HttpContext.Current.Error when deciding whether to rollback.

Is there a better solution? And what scope NHibernate transaction should have?

like image 372
Andrey Shchekin Avatar asked Oct 26 '09 17:10

Andrey Shchekin


2 Answers

I posted this a while ago:

http://groups.google.com/group/autofac/browse_thread/thread/f10badba5fe0d546/e64f2e757df94e61?lnk=gst&q=transaction#e64f2e757df94e61

Modified, so that the interceptor has logging capability and [Transaction] attribute can also be used on a class.

[global::System.AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class TransactionAttribute : Attribute
{
}


public class ServicesInterceptor : Castle.Core.Interceptor.IInterceptor
{
    private readonly ISession db;
    private ITransaction transaction = null;

    public ServicesInterceptor(ISession db)
    {
        this.db = db;
    }

    public void Intercept(IInvocation invocation)
    {
        ILog log = LogManager.GetLogger(string.Format("{0}.{1}", invocation.Method.DeclaringType.FullName, invocation.Method.Name));

        bool isTransactional = IsTransactional(invocation.Method);
        bool iAmTheFirst = false;

        if (transaction == null && isTransactional)
        {
            transaction = db.BeginTransaction();
            iAmTheFirst = true;
        }

        try
        {
            invocation.Proceed();

            if (iAmTheFirst)
            {
                iAmTheFirst = false;

                transaction.Commit();
                transaction = null;
            }
        }
        catch (Exception ex)
        {
            if (iAmTheFirst)
            {
                iAmTheFirst = false;

                transaction.Rollback();
                db.Clear();
                transaction = null;
            }

            log.Error(ex);
            throw ex;
        }
    }

    private bool IsTransactional(MethodInfo mi)
    {
        var atrClass = mi.DeclaringType.GetCustomAttributes(false);

        foreach (var a in atrClass)
            if (a is TransactionAttribute)
                return true;

        var atrMethod = mi.GetCustomAttributes(false);

        foreach (var a in atrMethod)
            if (a is TransactionAttribute)
                return true;

        return false;
    }
}
like image 94
dmonlord Avatar answered Nov 02 '22 11:11

dmonlord


When I use autofac I use the same container scoped method but instead of passing the same session to my Repository/DAO objects I pass an UnitOfWork that is container scoped. The Unit of work has this in the constructor.

    private readonly ISession _session;
    private ITransaction _transaction;

    public UnitOfWork(ISession session)
    {
        _session = session;
        _transaction = session.BeginTransaction();
    }

And the dispose is:

    public void Dispose()
    {
        try
        {
            if (_transaction != null &&
                            !_transaction.WasCommitted &&
                            !_transaction.WasRolledBack)
                _transaction.Commit();
            _transaction = null;
        }
        catch (Exception)
        {
            Rollback();
            throw;
        }

    }

I am (ab)using the deterministic disposal stuff in autofac in order to manage this, and well I sort of like it.

The other thing is that I am basically only targeting an ASPNet environment and made a conscious decision that a transaction is tied to a web request. So a transaction per web request pattern.

Because of that I can do this error handling code in an IHttpModule:

    void context_Error(object sender, System.EventArgs e)
    {
        _containerProvider.RequestContainer.Resolve<IUnitOfWork>().Rollback();
    }

I haven't taken a look at NHibernate.Burrow too closely but I'm sure there is something there that does most of this.

like image 42
Min Avatar answered Nov 02 '22 12:11

Min