Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When to commit NHibernate transactions in ASP.NET MVC 2 application?

First, some background: I'm new to ASP.NET MVC 2 and NHibernate. I'm starting my first application and I want to use NHibernate, because I come from JSP + Struts 1 + Hibernate web applications.

No one seems to be talking about this, so I guess it must be pretty obvious. Still I scratch my head because I can't find a solution that accomplish the following things:

1) I want to use the "session per request" strategy. So, everytime a user makes a request, he gets an Nhibernate session, starts a transaction, and when the request is over, the transaction commits, and the NHibernate session closes (and returns to the pool if there is one). This guarantees that my transactions are atomic.

2) When a database exception occurs (PK violation, unique violation, whatever) I want to capture that exception, rollback my transaction and give the user a explicit message: if it was PK violation, then that message, and the same with all integrity errors.

So, what is my problem? I come from the Java World, where I used a Filter to open the session, start the transaction, process the request, then commit the transaction and close the session. This works, except when an DB exception occurs, and by the time you are in the filter there's no way to change the destination page because the response is already committed.

So the user sees the success page when in reality the transaction was rollbacked. To avoid this I have to write a lot of data integrity checks in Java in order to prevent all integrity exceptions, because I could not handle them correctly. This is bad because I'm doing the work instead of leaving it to the database (or maybe I'm wrong and I always have to write all this data integrity code in my app?).

So I've found the IHttpModule interface which I'm guessing is pretty much the same concept as a javax.servlet.Filter (correct me if I'm wrong), so I'm guessing I could have the same problem again.

Where should I put my commits in order to make sure that my transactions are atomic, and when they throw exceptions I can capture them and change my destination page and give the user a comprehensive message?

So far the only possible solution I've come up with is to keep my IHttpModule to start and close the transaction, and put the commit calls in the last line of my controllers methods, thus being able to capture exceptions there and then return an appropiate view with the message. Now I would have to copy those commit and exception handling lines into all my controller methods that require commits. And there is the separation of concerns issue, that my controllers have to know about DB, which I don't like at all.

Is there a better way?

like image 287
Juan Paredes Avatar asked Nov 14 '22 08:11

Juan Paredes


1 Answers

If you're using ASP.NET MVC, you could use an ActionFilter to achieve the same effect.

Something like (this is hacked together from difference pieces of my architecture):

public class TransactionalAttribute : ActionFilterAttribute, IAuthorizationFilter, IExceptionFilter
{

    ITransaction transaction = NullTransaction.Instance;
    public IsolationLevel IsolationLevel { get; set; } 

    public TransactionalAttribute() 
    {
        IsolationLevel = IsolationLevel.ReadCommitted;
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        try
        {
            transaction.Commit();
            transaction = NullTransaction.Instance;
        }
        catch (Exception exception)
        {
            Log.For(this).FatalFormat("Problem trying to commit transaction {0}", exception);
        }

    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (transaction == NullTransaction.Instance) transaction = UnitOfWork.Current.BeginTransaction(IsolationLevel);
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (filterContext.Result != null) return;

        transaction.Commit();
        transaction = NullTransaction.Instance;
    }

    public void OnAuthorization(AuthorizationContext filterContext)
    {
        transaction = UnitOfWork.Current.BeginTransaction(IsolationLevel);
    }

    public void OnException(ExceptionContext filterContext)
    {
        try
        {
            transaction.Rollback();
            transaction = NullTransaction.Instance;
        }
        catch (Exception exception)
        {
            Log.For(this).FatalFormat("Problem trying to rollback transaction {0}", exception);
        }
    }

    private class NullTransaction : ITransaction
    {
        public static ITransaction Instance { get { return Singleton<NullTransaction>.Instance; } }

        public void Dispose()
        {

        }

        public void Commit()
        {
        }

        public void Rollback()
        {
        }
    }
}
like image 79
kͩeͣmͮpͥ ͩ Avatar answered May 14 '23 05:05

kͩeͣmͮpͥ ͩ