Creating an nhibernate session per web request with Castle.Facility.AutoTx and Castle.Facility.NHibernate

1.5k Views Asked by At

I am using Castle Windors and it's AutoTx and the NHibernate Facility by haf. Ultimately I want the benefits of ease of use of the Transaction attribute provided by AutoTx. (ASP.NET MVC 4 project).

I am using Castle.Facilities.NHibernate.ISessionManager to manage my PerWebRequest sessions. I have setup the Windsor Installer as such:

public void Install(Castle.Windsor.IWindsorContainer container, Castle.MicroKernel.SubSystems.Configuration.IConfigurationStore store)
        {
            container.AddFacility<AutoTxFacility>();
            container.Register(Component.For<INHibernateInstaller>().ImplementedBy<NHibernateInstaller>().LifeStyle.Singleton);            
            container.AddFacility<NHibernateFacility>(f => f.DefaultLifeStyle = DefaultSessionLifeStyleOption.SessionPerWebRequest);

            container.Install(FromAssembly.Containing<PersonRepository>());
        }

I'm using the DefaultLifeStyle of SessionPerWebRequest, which I expect would do exactly that, provide me with a session that lasts the entire web request, such that all calls to OpenSession on SessionManager within same request use the same session. I'm testing that with the following code:

public class HomeController : Controller
{        
    private readonly ISessionManager _sessionManager;

    public HomeController(ISessionManager sessionManager)
    {            
        _sessionManager = sessionManager;
    }

    public ActionResult Index()
    {
        using (var session1 = _sessionManager.OpenSession())
        {
            var person = session1.Get<Person>(1);
            using (var session2 = _sessionManager.OpenSession())
            {
                var person2 = session2.Get<Person>(1);
            }
        }

        return View();
    }        
}

and checking the log to see the id of each created session. The id is always different. eg

05/01/2013 11:27:39.109 DEBUG 9 NHibernate.Impl.SessionImpl - [session-id=c1ba248a-14ba-4468-a20c-d6114b7dac61] opened session at timestamp: 634929820591, for session factory: [/ea869bb12b4d4e51b9f431a4f9c9d9fa]
05/01/2013 11:30:36.383 DEBUG 9 NHibernate.Impl.SessionImpl - [session-id=72481180-625d-4085-98e9-929e3fd93e8a] opened session at timestamp: 634929822363, for session factory: [/ea869bb12b4d4e51b9f431a4f9c9d9fa]

It's worth noting that I haven't added anything to the web.config in the way of Handlers. Do I need to? ( I didn't see any documentation suggesting this in the NHib Facility Wiki) Are my expectations that the same Session will always be returned incorrect.

I've had a look through the source code for the facility and do not understand how a session per web request is being instantiated and how multiple calls to OpenSession would result in the same session in the same web request.

The following is how SessionManager is registered with Windsor:

Component.For<ISessionManager>().Instance(new SessionManager(() =>
                    {
                        var factory = Kernel.Resolve<ISessionFactory>(x.Instance.SessionFactoryKey);
                        var s = x.Instance.Interceptor.Do(y => factory.OpenSession(y)).OrDefault(factory.OpenSession());
                        s.FlushMode = flushMode;
                        return s;
                    }))
                        .Named(x.Instance.SessionFactoryKey + SessionManagerSuffix)
                        .LifeStyle.Singleton

ISession is registered with Windsor using the following

private IRegistration RegisterSession(Data x, uint index)
{
    Contract.Requires(index < 3,
                      "there are only three supported lifestyles; per transaction, per web request and transient");
    Contract.Requires(x != null);
    Contract.Ensures(Contract.Result<IRegistration>() != null);

    return GetLifeStyle(
        Component.For<ISession>()
            .UsingFactoryMethod((k, c) =>
            {
                var factory = k.Resolve<ISessionFactory>(x.Instance.SessionFactoryKey);
                var s = x.Instance.Interceptor.Do(y => factory.OpenSession(y)).OrDefault(factory.OpenSession());
                s.FlushMode = flushMode;
                logger.DebugFormat("resolved session component named '{0}'", c.Handler.ComponentModel.Name);
                return s;
            }), index, x.Instance.SessionFactoryKey);
}

private ComponentRegistration<T> GetLifeStyle<T>(ComponentRegistration<T> registration, uint index, string baseName)
            where T : class
{
    Contract.Requires(index < 3,
                      "there are only three supported lifestyles; per transaction, per web request and transient");
    Contract.Ensures(Contract.Result<ComponentRegistration<T>>() != null);

    switch (defaultLifeStyle)
    {
        case DefaultSessionLifeStyleOption.SessionPerTransaction:
            if (index == 0)
                return registration.Named(baseName + SessionPerTxSuffix).LifeStyle.PerTopTransaction();
            if (index == 1)
                return registration.Named(baseName + SessionPWRSuffix).LifeStyle.PerWebRequest;
            if (index == 2)
                return registration.Named(baseName + SessionTransientSuffix).LifeStyle.Transient;
            goto default;
        case DefaultSessionLifeStyleOption.SessionPerWebRequest:
            if (index == 0)
                return registration.Named(baseName + SessionPWRSuffix).LifeStyle.PerWebRequest;
            if (index == 1)
                return registration.Named(baseName + SessionPerTxSuffix).LifeStyle.PerTopTransaction();
            if (index == 2)
                return registration.Named(baseName + SessionTransientSuffix).LifeStyle.Transient;
            goto default;
        case DefaultSessionLifeStyleOption.SessionTransient:
            if (index == 0)
                return registration.Named(baseName + SessionTransientSuffix).LifeStyle.Transient;
            if (index == 1)
                return registration.Named(baseName + SessionPerTxSuffix).LifeStyle.PerTopTransaction();
            if (index == 2)
                return registration.Named(baseName + SessionPWRSuffix).LifeStyle.PerWebRequest;
            goto default;
        default:
            throw new FacilityException("invalid index passed to GetLifeStyle<T> - please file a bug report");
    }
}

which does register an ISession as PerWebRequest, but I can't see anywhere in the code where that named registration is extracted when a session is required?

Any help on what I need to do get Session per web request working is appreciated.

UPDATE I decided to just replace the code function being passed into SessionManager constructor with code that grabs the ISession from the container, rather than uses the factory. Works perfectly for what I want it to do, including being wrapped in transactions and only opening one session per web request, or transient etc.

Component.For<ISessionManager>().Instance(new SessionManager(() =>
{
    var s = Kernel.Resolve<ISession>();
    s.FlushMode = flushMode;
    return s;
}))
//Component.For<ISessionManager>().Instance(new SessionManager(() =>
//{
//    var factory = Kernel.Resolve<ISessionFactory>(x.Instance.SessionFactoryKey);
//    var s = x.Instance.Interceptor.Do(y => factory.OpenSession(y)).OrDefault(factory.OpenSession());
//    s.FlushMode = flushMode;
//    return s;
//}))
    .Named(x.Instance.SessionFactoryKey + SessionManagerSuffix)
    .LifeStyle.Singleton

Kernel.Resolve() I expect will grab the first registered service in the container. This will be whatever I set the Lifestyle to.

0

There are 0 best solutions below