views:

979

answers:

3

I'm running into issues with my ISessions in NHibernate. I keep getting "Session Closed!" errors. Can some one please show me the correct pattern including a definiion of the following methods and when to use each:

ISession.Close()
ISession.Dispose()
ISession.Disconnect()

Here's my problem. I have a callback setup to fireoff a process that awards badges to players every couple of minutes. However I keep getting "Session Closed!" errors or errors about not being able to associate collections.

Here's my Repository:

public class NHibernateRepository : IRepository
{
#region Fields

private ISession _session;
private readonly ISessionFactory _sessionFactory;
#endregion

#region Constructors

public NHibernateRepository(ISessionFactory sessionFactory)
{
    _sessionFactory = sessionFactory;
}

#endregion

#region IRepository Implementation

public ISession OpenSession()
{
    _session = _sessionFactory.OpenSession();
    return _session;
}

public IQueryable<TModel> All<TModel>()
{
    return _session.Linq<TModel>();
}

public void Save<TModel>(TModel model)
{
    _session.Save(model);
}
public void Update<TModel>(TModel model)
{
    _session.Update(model);
}
public void Delete<TModel>(TModel model)
{
    _session.Delete(model);
}

public ITransaction BeginTransaction()
{
    return _session.BeginTransaction();
}
public void Flush()
{
    _session.Flush();
}
#endregion

}

Here's my usage. The repository is getting injected via Structure Map

private Object _awardBadgesLock = new object(); //In case the callback happens again before the previous one completes

public void AwardBadges()
{

    lock (_awardBadgesLock)
    {
        using(session = _repository.OpenSession())
        {
            foreach (var user in _repository.All<User>().ToList())
            {
                var userPuzzles = _repository.All<Puzzle>().ByUser(user.Id).ToList();
                var userVotes = _repository.All<Vote>().Where(x => x.UserId == user.Id).ToList();
                var userSolutions = _repository.All<Solution>().ByUser(user.Id).ToList().Where(x => !userPuzzles.Select(y => y.Id).Contains(x.PuzzleId));
                var ledPuzzles = GetPuzzlesLedByUser(user.Id);

                AwardPlayerBadge(user, userSolutions);
                AwardCriticBadge(user, userVotes);
                AwardCreatorBadge(user, userPuzzles);
                AwardRidlerBadge(user, userPuzzles);
                AwardSupporterBadge(user, userVotes);
                AwardPopularBadge(user, userPuzzles);
                AwardNotableBadge(user, userPuzzles);
                AwardFamousBadge(user, userPuzzles);
                AwardLeaderBadge(user, ledPuzzles);

                using (var tx = _repository.BeginTransaction())
                {
                    _repository.Update(user);
                    tx.Commit();
                }
            }
        }
    }

}
+3  A: 

You should always use session.Dispose(); The other are for very strange occurances

Ayende Rahien
I am using the "using" statement which calls dispose. I'm still getting the error though and it doesn't happen all the time. Just some of the time.
Micah
+2  A: 

I advice you to read the documentation of ISession on https://nhibernate.svn.sourceforge.net/svnroot/nhibernate/trunk/nhibernate/src/NHibernate/ISession.cs

Anyway the proper way to clean up when you are finished with the session is to dispose it (or better, surround the usage with using statement). In this case, "using" closes the session and suppresses the finalizer, i.e. it prevents the session object from unnecessarily surviving the next garbage collecting and saves the memory.

If the connection is already closed, disposing it will not throw an exception. On the other hand, closing after disposing (or after closing) throws an exception.

The documentation recommends calling disconnect instead of closing, because this releases the connection to the connection pool. You should call Reconnect before using a disconnected session.

For my needs, I always use "using" which calls Dispose and have never used the othe two functions.

Mouk
But he's using 'using' isn't he? ... using(session = _repository.OpenSession())
UpTheCreek
A: 

The issue lies in the fact the ISession is not thread-safe. There were multiple methods being fired on separate threads that all created an instance of ISession. The issue was really with the fact that they all shared the same SessionFactory. Image both of these methods are fired off on separate threads:

ISessionFactory _sessionFactory;

void MethodOne()
{
   using(ISession session = _sessionFactory.OpenSession()) 
   {
       //Do something with really quick with the session
       //Then dispose of it
   }
}

void MethodTwo()
{
   //OpenSession() actually returns the same instance used in the 
   //previous method that has already disposed of the object;
   using(ISession session = _sessionFactory.OpenSession()) 
   {
       //Do something with a session that has already been disposed
       //throws errors

   }
}

How I fixed it was basically ditching NHIbernate in these scenarios and called stored procs instead. I think it turned out to be more performant in my situation anyway.

Micah
Interesting - which `ISessionFactory` implementation are you using? The [interface's documentation](https://nhibernate.svn.sourceforge.net/svnroot/nhibernate/trunk/nhibernate/src/NHibernate/ISessionFactory.cs) says "Implementors must be threadsafe."
Jeff Sternal
That's exactly the problem. "Implementors must be threadsafe" because SessionFactory is not thread-safe.
Micah
Right, but *which* concrete `ISessionFactory` implementation? `SessionFactoryImpl`? I'm sure if you can reproduce the problem (or better yet, locate the bug [in the source code](https://nhibernate.svn.sourceforge.net/svnroot/nhibernate/trunk/nhibernate/src/NHibernate/Impl/SessionFactoryImpl.cs)), the NHibernate team would be interested in hearing about it.
Jeff Sternal