ActionFilter for Nhibernate transaction management is a good way to go

I have the following shell:

 public interface ITransactionScopeWrapper : IDisposable
{
    void Complete();
}

public class TransactionScopeWrapper : ITransactionScopeWrapper
{
    private readonly TransactionScope _scope;
    private readonly ISession _session;
    private readonly ITransaction _transaction;

    public TransactionScopeWrapper(ISession session)
    {
        _session = session;
        _scope = new TransactionScope(TransactionScopeOption.Required,
                                      new TransactionOptions {IsolationLevel = IsolationLevel.ReadCommitted});
        _transaction = session.BeginTransaction();
    }

    #region ITransactionScopeWrapper Members

    public void Dispose()
    {
        try
        {
            _transaction.Dispose();
        }
        finally
        {
            _scope.Dispose();
        }
    }

    public void Complete()
    {
        _session.Flush();
        _transaction.Commit();
        _scope.Complete();
    }

    #endregion
}

      

In my ActionFilter I have the following:

 public class NhibernateTransactionAttribute : ActionFilterAttribute
{
    public ITransactionScopeWrapper TransactionScopeWrapper { get; set; }
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
    }
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        TransactionScopeWrapper.Complete();

        base.OnActionExecuted(filterContext);
    }

}

      

I am using Castle to manage my ISession using lifestyles for every web request:

container.Register(
            Component.For<ISessionFactory>().UsingFactoryMethod(
                x => x.Resolve<INHibernateInit>().GetConfiguration().BuildSessionFactory()).LifeStyle.Is(
                    LifestyleType.Singleton));

container.Register(
            Component.For<ISession>().UsingFactoryMethod(x => container.Resolve<ISessionFactory>().OpenSession()).
                LifeStyle.Is(LifestyleType.PerWebRequest));

container.Register(
          Component.For<ITransactionScopeWrapper>().ImplementedBy<TransactionScopeWrapper>().LifeStyle.Is(
              LifestyleType.PerWebRequest));

      

So now for my questions.

  • Any problems managing the transaction this way
  • Does the ActionFilter use the OnActionExecuting and OnActionExecuted methods on the same thread.

I'm asking for number 2 because BeginRequest and EndRequest are not guaranteed to run on the same thread, and if you forward transactions to them, you will run into big problems.

In my ActionFilter TransactionScopeWrapper property is injected.

+3


source to share


2 answers


There are other aspects that you should study as well.

First I would say to decide where to dispose of your transaction. Be aware that if you are using lazy loading and passing a data object back to your view and accessing a property or link that is configured to be lazy, you will run into problems because your transaction is already closed in your OnActionExecuted

. Although as far as I know you should only use viewmodels in your views, sometimes the entity is a little more convenient. Regardless of the reason, if you want to use lazy loading and access it in your views, you will need to move the transaction completion into a method OnResultExecuted

so that it is not set early.

Second, you should also check if there were any exceptions or model errors before committing the transaction. I ended up using here and here for my final Filter to work with my nHibernate transaction.

Third, if you decide to dispose of your transaction in a handler OnResultExecuted

, that you don't, if it's a request for a child action. The reason is that, like you, I included the session in the web request, but found that the child activities are not considered a new request, and when they are called and they try to open their own session, they get the already open session context instead. When the child activity is complete, it tries to close the ITS session, but actually closes the session that is also used by the parent view. This triggered any logic after the child action based on lazy loaded data also failed.



I would love to go through and try to remove my lazy loaded data from my app when it comes to views, but I don’t have time to let you know about these issues that may arise.

I was about to post my own action filter when I realized I have some DRY issues that I need to fix. suffice it to say that I check what filterContext.Exception

and filterContext.ExceptionHandled

to see if there were any errors and if they have already been handled. Note that just because a handled exception does not mean your transaction is okay to be committed. And while this is more subjective since your application is coded, you can also check filterContext.Controller.ViewData.ModelState.IsValid

before committing a transaction.

UPDATE: Unlike you, I am using StructureMap, not Castle for Dependency Injection, but in my case, I added this line to my Application_EndRequest method in the gobal.asax file as the last bit of cleanup, I am assuming something similar in Castle ? StructureMap.ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects();

UPDATE 2: More direct answer to your question anyway. I don't see anything wrong with using the wrapper as you chose it, although I'm not sure why you feel the need to wrap it? nHibernate does a really good job of handling the transaction itself, so another layer of abstraction around that seems unnecessary to me. You can just as easily start a transaction in yours OnActionExecuting

and explicitly fill it in OnActionExecuted

. By fetching the ISession object through DependencyResolver

, you eliminate any issues you might have with threading, since I believe the IoC container is thread safe and from there you can get your current transaction with Session.Transaction

and check its current state withIsActive

... I understand that two methods are possible for different threads, especially when working with a class that inherits from AsynController

.

+5


source


I have a problem with this method. What does it do if you use "@ Html.Action (" TestMethod "," TestController ")"?

As for me, I prefer to use an explicit call to the transaction:



using (var tx = session.BeginTransaction())
{
    // perform your insert here
    tx.Commit();
}

      

What about thread safety, I would like to know too.

0


source







All Articles