使用NHibernate 進行Web 開發的朋友大多都知道Session-Per-Request 模式,但網上真正能夠正確使用的例子不多,網上包括園子裡好多文章犯了同一個錯誤,而這個錯誤確一直在散播...
先來看看園子裡Flyear 的一篇文章《NHibernate One Session Per Request 簡單實作》。
首先對NHibernate 進行設定沒有錯:
<property name='current_session_context_class'>web</property>
錯誤在類別NHinbernateSessionFactory(類別名稱都寫錯了) 中,NHinbernateSessionFactory.GetCurrentSession 不應包含對HttpContext 的操作,GetCurrentSession 其實本來就很簡單,請參閱(類別名稱改為簡短):
public sealed class NHibernateHelper
{
public static readonly ISessionFactory SessionFactory;
static NHibernateHelper()
{
SessionFactory = new Configuration()
.Configure()
.AddAssembly(/**/)
.BuildSessionFactory();
}
public static ISession GetCurrentSession()
{
return SessionFactory.GetCurrentSession();
}
}
將current_session_context_class 設定為web,NHibernate 初始化時會產生NHibernate.Context.WebSessionContext 類別的實例,WebSessionContext 類別原始碼如下:
[Serializable]
public class WebSessionContext : MapBasedSessionContext
{
// Fields
private const string SessionFactoryMapKey = "NHibernate.Context.WebSessionContext.SessionFactoryMapKey";
// Methods
public WebSessionContext(ISessionFactoryImplementor factory) : base(factory)
{ }
protected override IDictionary GetMap()
{
return (HttpContext.Current.Items[SessionFactoryMapKey] as IDictionary);
}
protected override void SetMap(IDictionary value)
{
HttpContext.Current.Items[SessionFactoryMapKey] = value;
}
}
WebSessionContext 實作了Session-Per-Request 模式,它封裝了HttpContext ,因此我們不需要在我們的輔助類別(NHibernateSessionFactory 或是NHibernateHelper)中再對HttpContext 進行操作。
我們只需要從WebSessionContext 的實例中取得Session 即可。從WebSessionContext 類別取得目前ISession 相當簡單,因為WebSessionContext 實作了ICurrentSessionContext 介面:
public interface ICurrentSessionContext
{
ISession CurrentSession();
}
NHibernate.Context 命名空間中的類別和接口
(說明:current_session_context_class 也可以配置為Managed_web、Call、thread_static,分別對應類別ManagedWebSessionContext、CallSessionContext、ThreadStaticSessionContext)
在實際使用中我們並不需要直接呼叫WebSessionContext 的CurrentSession() 方法,因為ISessionFactory 提供了一個更簡單的方法讓我們能一步取得到Session:
public interface ISessionFactory : IDisposable
{
ISession GetCurrentSession();
//......
}
以下探討ISessionFactory.GetCurrentSession 方法的具體實作:
Configuration.BuildSessionFactory 方法實際回傳的是SessionFactoryImpl 類別的實例,讓我們簡單看一下SessionFactoryImpl 的部分程式碼吧:
1 public sealed class SessionFactoryImpl
2 : ISessionFactoryImplementor, IMapping, ISessionFactory, IDisposable, IObjectReference
3 {
4 private readonly ICurrentSessionContext currentSessionContext;
5
6 public ISession GetCurrentSession()
7 {
8 if (this.currentSessionContext == null)
9 {
10 throw new HibernateException(
11 "No CurrentSessionContext configured (set the property current_session_context_class)!");
12 }
13 return this.currentSessionContext.CurrentSession();
14 }
15
16 public ICurrentSessionContext CurrentSessionContext
17 {
18 get { return this.currentSessionContext; }
19 }
20
21 private ICurrentSessionContext BuildCurrentSessionContext()
22 {
23 string name = PropertiesHelper.GetString("current_session_context_class", this.properties, null);
24 string str2 = name;
25 if (str2 != null)
26 {
27 if (str2 == "call") return new CallSessionContext(this);
28 if (str2 == "thread_static") return new ThreadStaticSessionContext(this);
29 if (str2 == "web") return new WebSessionContext(this);
30 if (str2 == "managed_web") return new ManagedWebSessionContext(this);
31 }
32 else
33 return null;
34 try
35 {
36 Type type = ReflectHelper.ClassForName(name);
37 return (ICurrentSessionContext)Environment.BytecodeProvider.ObjectsFactory
38 .CreateInstance(type, new object[] { this });
39 }
40 catch (Exception exception)
41 {
42 log.Error("Unable to construct current session context [" + name + "]", exception);
43 return null;
44 }
45 }
46 //......
47 }
SessionFactoryImpl 在實例化時會呼叫BuildCurrentSessionContext() 方法(行21)為currentSessionContext 欄位賦值,具體值有設定檔中的current_session_context_class 決定。
ISessionFactory 的GetCurrentSession() 呼叫的是ICurrentSessionContext 的CurrentSession()(行6) 方法。
上面的程式碼相當簡單,想必大家都已經明明白白了。
《NHibernate One Session Per Request 簡單實現》 一文中還有兩個不太恰當的地方:
1. 並不是每個請求都需要一個Session 來存取資料庫。文中Global.asax 的程式碼給所有的請求在開始的時候都進行WebSessionContext.Bind(),會照成很多Session 的浪費,雖然NHibernate 的Session 是輕量級的,較為合理的做法是在“真正需要”時綁定。
2. 因為WebSessionContext.Unbind 方法需要一個ISessionFactory 介面的實例,迫使用我們的輔助類別(NHibernateSessionFactory 或是NHibernateHelper)來公開SessionFactory。
第一個問題比較容易解決,留在回覆中和大家交流吧。
第二個問題我會在下一篇隨筆中解答。
本人學習NHibernate 時間也不長,屬於新手,如有錯誤,歡迎指正!