项目中引入 NHibernate 的时间不长,在将项目中的数据访问层代码改写成NH版本之后,遇到了 failed to lazily initialize a collection, no session or session was closed  异常。

异常起因

是在实体多对多、延迟(many-to-many lazy) 加载的过程中,调用对应字段则会抛出上述异常。后来经调试,查找资料发现这是由于我在代码中使用了 using(ISeesion session = SessionFactory.CreateSession()) 的缘故,因为 using 语句强制释放资源,当lazy load 的时候就出现了问题。

思考过程

many-to-many 的lazy load 非常常见,是经常使用的功能,既然在数据层中的GetXXX()方法中不能使用using等方式来释放资源,想必就需要手动的进行释放,当实体对象使用完毕之后,在进行Session的资源回收。

这确实很麻烦,首先我不想在我们的页面逻辑部分还会出现 ISession 等对象,但是同时又需要资源释放,这两者之间是互斥的,因为你既不想这个对象出现,而你需要手动控制这个对象。

在思考的过程中,我也想过可不可以使用 HttpModule 在BeginRequest 的时候初始化 ISession ,EndRequest 的时候关闭 ISession。但是我想,我们不能在每一次请求就各开关一次 ISession 吧,因为并不是所有的页面都有数据访问的任务。这样的开销是一个问题。

解决方案

  自己之前也没有接触太多的NH,无奈之中,在StackOverflow 上面问了一下,有人给了一个链接,CodeProject 上面的一篇文章《NHibernate Best Practices with ASP.NET, 1.2nd Ed》下载了其中的代码发现,他也是使用HttpModule 来实现的,看了一下正文的解释,原来我的性能上面的顾虑是多余的。

整体的思路如下:

HttpModule

  BeginRequest:获取ISession对象,将其放入HttpContext.Items 中

  EndRequest:关闭ISession 对象。

DataAccess

  通过SessionManager 获得ISession 对象,SessionManager 从HttpContext 中得到 ISession。其中SessionManager 使用了Lazy singleton 模式

总结

  整个解决过程不算繁琐,全部重构了项目的代码也并没有花费太多的时间(紧紧是吧 using 语句去掉),在查找解决方案的过程中,中文资源中很少有人提到这个最佳实践,把我遇到的问题罗列出来,也和大家分享一下,

:D