NHibernate,奇怪的“会话已关闭!”错误

时间:2010-04-03 17:21:25

标签: c# asp.net nhibernate ninject

注意:现在我输入了这个,我不得不为超长问题道歉,但是,我认为这里提供的所有代码和信息都是相关的。


好吧,我在ASP.NET webforms应用程序中的随机点上得到了奇怪的“Session Is Closed”错误。然而,今天,它终于在同一个地方一次又一次地发生。我几乎可以确定在我的代码中没有任何处理或关闭会话,因为使用的代码位远远超出了所有其他代码,如下所示。

我也使用ninject作为我的IOC,这可能/可能不重要。

好的,首先是我的SessionFactoryProviderSessionProvider类:


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();                
        }
    }
}

这两个类与Ninject连接如下:

NHibernateModule

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

据我所知,按预期工作。

现在我的BaseDao<T>班级:


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>();
    }        
}

在Ninject中绑定的是这样的:


DaoModule

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

现在导致这种情况的Web请求是在我保存对象的时候,直到今天我做了一些模型更改才发生,但是我的模型的更改并没有改变数据访问代码。虽然它改变了一些NHibernate映射(如果有人有兴趣我也可以发布这些映射)

据我所知,BaseDao<SomeClass>.Get被调用,然后调用BaseDao<SomeOtherClass>.Get,然后调用BaseDao<TypeImTryingToSave>.Save

这是Save()

中第三次调用
using (var transaction = session.BeginTransaction())

失败并且“会话已关闭!”或者更确切地说是例外:

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'.

确实在调试器上显示第三次从SessionProvider请求会话时,它确实已关闭且未连接。

我已经确认我的DisposeSessionFactoryProvider上的SessionProvider在请求结束时被调用,而不是在我的Dao上Save来电之前

所以现在我有点卡住了。想到一些事情。

  • 我做了什么明显错误的事吗?
  • NHibernate会在没有我要求的情况下关闭会议吗?
  • 关于我可能会做什么的任何变通办法或想法?

提前致谢

3 个答案:

答案 0 :(得分:19)

ASP.NET是多线程的,因此对ISession的访问必须是线程安全的。假设您正在使用每个请求的会话,最简单的方法是使用NHibernate对contextual sessions的内置处理。

首先配置NHibernate以使用Web会话上下文类:

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();

然后使用ISessionFactory.GetCurrentSession()获取现有会话,或者将新会话绑定到工厂(如果不存在)。下面我将剪切+粘贴我的代码以打开和关闭会话。

    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();
            }
        }
    }        

答案 1 :(得分:7)

在为我正在处理的Web API项目运行集成测试时,我间歇性地遇到了Session Closed / ObjectDisposedExceptions。

这里没有任何提示解决了它,所以我构建了一个NHibernate的调试版本来了解更多信息,当控制器的Get函数返回{时,我看到它正在执行linq查询{1}}响应对象。

事实证明,我们的存储库正在执行linq查询并返回域对象的IEnumerable,但从不调用IEnumerable来强制查询查询。该服务反过来返回ToList(),并且Controller将可枚举包装在响应对象中并返回它。

在返回期间,linq查询最终被执行,但NHibernate会话在某个时间点暂时关闭,因此它抛出异常。解决方案是始终确保在返回服务之前在使用会话块中的存储库中调用IEnumerable

答案 2 :(得分:3)

我建议你SessionImpl.Close / SessionImpl.Dispose set a breakpoint,看看是谁通过堆栈跟踪调用它。您也可以为自己构建一个NH的调试版本并执行相同的操作。