我一直在使用NHibernate一段时间,并且不时发现如果我尝试同时请求两个页面(或尽可能接近),它偶尔会出错.所以我认为这是因为我的会话管理不是线程安全的.
我认为这是我的课程,所以我尝试使用此博客文章中的不同方法http://pwigle.wordpress.com/2008/11/21/nhibernate-session-handling-in-aspnet-the-easy-way/但是我仍然遇到同样的问题.我得到的实际错误是:
Server Error in '/AvvioCMS' Application. failed to lazily initialize a collection, no session or session was closed 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: NHibernate.LazyInitializationException: failed to lazily initialize a collection, no session or session was closed
这个或没有datareader是开放的,但这是罪魁祸首.
我把会话管理课放在下面,有人能说出我为什么会遇到这些问题吗?
public interface IUnitOfWorkDataStore { object this[string key] { get; set; } } public static Configuration Init(IUnitOfWorkDataStore storage, Assembly[] assemblies) { if (storage == null) throw new Exception("storage mechanism was null but must be provided"); Configuration cfg = ConfigureNHibernate(string.Empty); foreach (Assembly assembly in assemblies) { cfg.AddMappingsFromAssembly(assembly); } SessionFactory = cfg.BuildSessionFactory(); ContextDataStore = storage; return cfg; } public static ISessionFactory SessionFactory { get; set; } public static ISession StoredSession { get { return (ISession)ContextDataStore[NHibernateSession.CDS_NHibernateSession]; } set { ContextDataStore[NHibernateSession.CDS_NHibernateSession] = value; } } public const string CDS_NHibernateSession = "NHibernateSession"; public const string CDS_IDbConnection = "IDbConnection"; public static IUnitOfWorkDataStore ContextDataStore { get; set; } private static object locker = new object(); public static ISession Current { get { ISession session = StoredSession; if (session == null) { lock (locker) { if (DBConnection != null) session = SessionFactory.OpenSession(DBConnection); else session = SessionFactory.OpenSession(); StoredSession = session; } } return session; } set { StoredSession = value; } } public static IDbConnection DBConnection { get { return (IDbConnection)ContextDataStore[NHibernateSession.CDS_IDbConnection]; } set { ContextDataStore[NHibernateSession.CDS_IDbConnection] = value; } } }
我正在使用的实际商店是这样的:
public class HttpContextDataStore : IUnitOfWorkDataStore { public object this[string key] { get { return HttpContext.Current.Items[key]; } set { HttpContext.Current.Items[key] = value; } } }
我使用以下命令初始化Application_Start上的SessionFactory:
NHibernateSession.Init(new HttpContextDataStore(), new Assembly[] { typeof(MappedClass).Assembly});
更新
嗨,大家好,感谢您的建议,我尝试了一些不同的事情来尝试和简化代码,但我仍然遇到同样的问题,我可能知道为什么.
我根据需要创建会话,但是在我的global.asax中,我在Application_EndRequest上处理会话.但是我发现当我在加载页面结束时进行调试时,Application_EndRequest被多次触发.我认为事件只是假设在请求的最后一次触发,但如果不是,并且其他一些项目正在尝试使用Session(这是错误所抱怨的),无论出于什么奇怪的原因是我的问题,Session仍然是线程安全的,它只是提前处理.
有人有任何想法吗?我做了谷歌,看到VS开发服务器确实导致这样的问题,但我通过IIS运行它.
虽然我没有看到你的整个代码库或你想要解决的问题,但是重新思考如何使用NHibernate可能是有序的.从文档:
创建NHibernate会话时应注意以下做法:
永远不要为每个数据库连接创建多个并发的ISession或ITransaction实例.
每个事务为每个数据库创建多个ISession时要格外小心.ISession本身会跟踪对加载的对象所做的更新,因此不同的ISession可能会看到过时的数据.
ISession 不是线程安全的!永远不要在两个并发线程中访问相同的ISession.ISession通常只有一个工作单元!
对于我所说的,最后一点是最相关的(在多线程环境的情况下很重要).对于小型原子操作,应该使用一次ISession然后处理.还来自文档:
ISessionFactory是一个昂贵的创建线程安全对象,旨在由所有应用程序线程共享.ISession是一种廉价的非线程安全对象,对于单个业务流程应该使用一次,然后丢弃.
结合这两个想法,而不是存储ISession本身,存储会话工厂,因为这是"大"对象.然后,您可以使用类似SessionManager.GetSession()的东西作为包装器从会话存储中检索工厂并实例化会话并将其用于一个操作.
在ASP.NET应用程序的上下文中,问题也不那么明显.您正在静态确定ISession对象的范围,这意味着它在AppDomain中共享.如果两个不同的页面请求是AppDomain中的生命周期内创建并同时执行的,你现在有两个页面(不同的线程)触摸同一的Isession这是不是安全的.
基本上,不要试图尽可能长时间地保持会话,尽量尽快摆脱它们,看看你是否有更好的结果.
编辑:
好的,我可以看到你想要去哪里.听起来你正在尝试实现Open Session In View模式,你可以采用几种不同的路线:
如果添加另一个框架不是问题,请查看类似Spring.NET的内容.它是模块化的,所以你不必使用整个东西,你可以只使用NHibernate助手模块.它支持视图模式中的打开会话.这里的文档(标题21.2.10."Web会话管理").
如果您愿意自己动手,请查看Bill McCafferty发布的代码项目:"NHibernate Best Practices".接下来,他描述了通过自定义IHttpModule实现模式.我也看过互联网上有关没有IHttpModule实现模式的帖子,但这可能是你一直在尝试的.
我通常的模式(也许你已经在这里跳过)是首先使用框架.它消除了许多令人头疼的问题.如果它太慢或不适合我的需要,那么我尝试调整配置或自定义它.只有在那之后我才会尝试自己滚动,但是YMMV.:)