NHibernate, and odd "Session is Closed!" errors
Asked Answered
E

3

19

Note: Now that I've typed this out, I have to apologize for the super long question, however, I think all the code and information presented here is in some way relevant.


Okay, I'm getting odd "Session Is Closed" errors, at random points in my ASP.NET webforms application. Today, however, it's finally happening in the same place over and over again. I am near certain that nothing is disposing or closing the session in my code, as the bits of code that use are well contained away from all other code as you'll see below.

I'm also using ninject as my IOC, which may / may not be important.

Okay, so, First my SessionFactoryProvider and SessionProvider classes:


SessionFactoryProvider

public class SessionFactoryProvider : IDisposable
{
    ISessionFactory sessionFactory;

    public ISessionFactory GetSessionFactory()
    {
        if (sessionFactory == null)
            sessionFactory =
                Fluently.Configure()
                        .Database(
                            MsSqlConfiguration.MsSql2005.ConnectionString(p =>
                                p.FromConnectionStringWithKey("QoiSqlConnection")))
                        .Mappings(m =>
                            m.FluentMappings.AddFromAssemblyOf<JobMapping>())
                        .BuildSessionFactory();

        return sessionFactory;
    }

    public void Dispose()
    {
        if (sessionFactory != null)
            sessionFactory.Dispose();
    }
}

SessionProvider

public class SessionProvider : IDisposable
{
    ISessionFactory sessionFactory;
    ISession session;

    public SessionProvider(SessionFactoryProvider sessionFactoryProvider)
    {
        this.sessionFactory = sessionFactoryProvider.GetSessionFactory();
    }

    public ISession GetCurrentSession()
    {
        if (session == null)
            session = sessionFactory.OpenSession();

        return session;
    }

    public void Dispose()
    {
        if (session != null)
        {
            session.Dispose();                
        }
    }
}

These two classes are wired up with Ninject as so:

NHibernateModule

public class NHibernateModule : StandardModule
{        
    public override void Load()
    {
        Bind<SessionFactoryProvider>().ToSelf().Using<SingletonBehavior>();
        Bind<SessionProvider>().ToSelf().Using<OnePerRequestBehavior>();
    }
}

and as far as I can tell work as expected.

Now my BaseDao<T> class:


BaseDao

public class BaseDao<T> : IDao<T> where T : EntityBase
{
    private SessionProvider sessionManager;
    protected ISession session { get { return sessionManager.GetCurrentSession(); } }

    public BaseDao(SessionProvider sessionManager)
    {
        this.sessionManager = sessionManager;
    }        

    public T GetBy(int id)
    {
        return session.Get<T>(id);
    }

    public void Save(T item)        
    {
        using (var transaction = session.BeginTransaction())
        {
            session.SaveOrUpdate(item);

            transaction.Commit();
        }
    }

    public void Delete(T item)
    {
        using (var transaction = session.BeginTransaction())
        {
            session.Delete(item);

            transaction.Commit();
        }
    }

    public IList<T> GetAll()
    {
        return session.CreateCriteria<T>().List<T>();
    }

    public IQueryable<T> Query()
    {
        return session.Linq<T>();
    }        
}

Which is bound in Ninject like so:


DaoModule

public class DaoModule : StandardModule
{
    public override void Load()
    {
        Bind(typeof(IDao<>)).To(typeof(BaseDao<>))
                            .Using<OnePerRequestBehavior>();
    }
}

Now the web request that is causing this is when I'm saving an object, it didn't occur till I made some model changes today, however the changes to my model has not changed the data access code in anyway. Though it changed a few NHibernate mappings (I can post these too if anyone is interested)

From as far as I can tell, BaseDao<SomeClass>.Get is called then BaseDao<SomeOtherClass>.Get is called then BaseDao<TypeImTryingToSave>.Save is called.

it's the third call at the line in Save()

using (var transaction = session.BeginTransaction())

that fails with "Session is Closed!" or rather the exception:

Session is closed!
Object name: 'ISession'.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. 

Exception Details: System.ObjectDisposedException: Session is closed!
Object name: 'ISession'.

And indeed following through on the Debugger shows the third time the session is requested from the SessionProvider it is indeed closed and not connected.

I have verified that Dispose on my SessionFactoryProvider and on my SessionProvider are called at the end of the request and not before the Save call is made on my Dao.

So now I'm a little stuck. A few things pop to mind.

  • Am I doing anything obviously wrong?
  • Does NHibernate ever close sessions without me asking to?
  • Any workarounds or ideas on what I might do?

Thanks in advance

Eniwetok answered 3/4, 2010 at 17:21 Comment(5)
Can you reproduce this in a relatively standalone unit test?Chippewa
please always post the full stack traceDilapidated
Transaction per each DAO operation? Such micromanagement might be not so good idea. What if you need to save or delete two DAOs in a single transaction? Read this for more explanations and ideas: msdn.microsoft.com/en-us/magazine/ee819139.aspxBarbee
@Martin Aye, I have since moved to a transaction per request model. It's been a while since I posted this question though :)Eniwetok
Though long, your question is very readable and clean. Keep it up!Prearrange
G
20

ASP.NET is multi-threaded so access to the ISession must be thread safe. Assuming you're using session-per-request, the easiest way to do that is to use NHibernate's built-in handling of contextual sessions.

First configure NHibernate to use the web session context class:

sessionFactory = Fluently.Configure()
    .Database(
        MsSqlConfiguration.MsSql2005.ConnectionString(p =>
            p.FromConnectionStringWithKey("QoiSqlConnection")))
    .Mappings(m => m.FluentMappings.AddFromAssemblyOf<JobMapping>())
    .ExposeConfiguration(x => x.SetProperty("current_session_context_class", "web")
    .BuildSessionFactory();

Then use the ISessionFactory.GetCurrentSession() to get an existing session, or bind a new session to the factory if none exists. Below I'm going to cut+paste my code for opening and closing a session.

    public ISession GetContextSession()
    {
        var factory = GetFactory(); // GetFactory returns an ISessionFactory in my helper class
        ISession session;
        if (CurrentSessionContext.HasBind(factory))
        {
            session = factory.GetCurrentSession();
        }
        else
        {
            session = factory.OpenSession();
            CurrentSessionContext.Bind(session);
        }
        return session;
    }

    public void EndContextSession()
    {
        var factory = GetFactory();
        var session = CurrentSessionContext.Unbind(factory);
        if (session != null && session.IsOpen)
        {
            try
            {
                if (session.Transaction != null && session.Transaction.IsActive)
                {
                    session.Transaction.Rollback();
                    throw new Exception("Rolling back uncommited NHibernate transaction.");
                }
                session.Flush();
            }
            catch (Exception ex)
            {
                log.Error("SessionKey.EndContextSession", ex);
                throw;
            }
            finally
            {
                session.Close();
                session.Dispose();
            }
        }
    }        
Gink answered 3/4, 2010 at 19:29 Comment(5)
well that solved the problem thanks :) Do you have a full explanation what was causing the problem? I assume with you mention of ASP.NET being multi-threaded (which I knew) was something to do with it? Normally can you not use the same session across threads then?Eniwetok
I ran into exactly the same problem when I started with session-per-request. Using NHibernate Profiler, I could see that sessions were opened for requests for linked resources, such as CSS and JavaScript files, as well as the web page. Occasionally one of these requests will trigger the EndRequest and cause the session to close while another thread in the same request has a reference to it (race condition).Gink
What calls EndContextSession()?Schreck
I call it in Global.asax Application_EndRequest. You could also use an HttpHandler.Gink
As of 12 Feb 2016 the link to documentation about contextual sessions is nhibernate.info/doc/nhibernate-reference/architecture.htmlTerzas
E
10

I ran into Session Closed/ObjectDisposedExceptions intermittently when running integration tests for a Web API project I was working on.

None of the tips here solved it, so I built a debug version of NHibernate to find out more, and I saw it doing a linq query when the controller's Get function was returning with the IEnumerable of response objects.

It turned out that our repository was doing a linq query and returning the IEnumerable of the domain objects, but never calling ToList() to force the evaluation of the query. The service in turn returned the IEnumerable, and the Controller wrapped the enumerable with response objects and returned it.

During that return, the linq query finally got executed, but the NHibernate session got closed in the interim at some point, so it threw an exception. The solution was to always make sure we call .ToList() in the repository within the using session block before we return to the service.

Exuberate answered 11/12, 2014 at 19:22 Comment(0)
G
4

I suggest you set a breakpoint on SessionImpl.Close / SessionImpl.Dispose and see who is calling it via the stack trace. You could also just build a debug version of NH for yourself and do the same.

Gapes answered 3/4, 2010 at 18:55 Comment(2)
Thanks for the tip, unfortunately the same thing is shown. SessionProvider.Dispose is calling SessionImpl.Dispose is calling SessionImpl.Close after the exception is thrown.Eniwetok
Which means, nothing during "execution time" is explicitly closing it, which means something fishy is going on...Eniwetok

© 2022 - 2024 — McMap. All rights reserved.