前言
1、本文以mvc3為例,借鑒開源項目 NerdDnner項目完成nhibernate中的 Session-per-request 模式,本文創建了一個自定義的httpmodel類,來實現在http請求的時候創建並開啟一個session並綁定到CurrentSessionContext中,當請求完成以后關閉,同時包含對事物的處理。
2、利用MiniProfiler.NHibernate來追蹤項目中的產生的sql,便於我們及時發現問題及時處理。MiniProfiler.NHibernate現在可以在nuget上直接獲取或者可以去github中下載源碼查看。
實現Session per request
public class NHibernateSessionPerRequest : IHttpModule { private static readonly ISessionFactory sessionFactory; //構造函數 static NHibernateSessionPerRequest() { sessionFactory = CreateSessionFactory(); } // 初始化httpmodel public void Init( HttpApplication context ) { context.BeginRequest += BeginRequest; context.EndRequest += EndRequest; } public void Dispose() { } public static ISession GetCurrentSession() { return sessionFactory.GetCurrentSession(); } // 打開session, 開啟事務, 綁定session到CurrentSessionContext private static void BeginRequest( object sender, EventArgs e ) { ISession session = sessionFactory.OpenSession(); session.BeginTransaction(); CurrentSessionContext.Bind( session ); } // 移除session會話, 事物提交, and 關閉session會話 private static void EndRequest( object sender, EventArgs e ) { ISession session = CurrentSessionContext.Unbind( sessionFactory ); if ( session == null ) return; try { session.Transaction.Commit(); } catch ( Exception ) { session.Transaction.Rollback(); } finally { session.Close(); session.Dispose(); } } // 創建sessionfactory private static ISessionFactory CreateSessionFactory() { return Fluently.Configure(new Configuration().Configure()) .Mappings( m => m.AutoMappings.Add( CreateMappings() ) ) .ExposeConfiguration( UpdateSchema ) .CurrentSessionContext<WebSessionContext>() .BuildSessionFactory(); } private static AutoPersistenceModel CreateMappings() { return AutoMap .Assembly( System.Reflection.Assembly.GetCallingAssembly() ) .Where( t => t.Namespace != null && t.Namespace.EndsWith( "Models" ) ) .Conventions.Setup( c => c.Add( DefaultCascade.SaveUpdate() ) ); } // 生成數據庫架構 private static void UpdateSchema( Configuration cfg ) { new SchemaUpdate( cfg ) .Execute( false, true ); } }
此類的nh的配置采用fluent的方式配置映射,可以生成數據庫架構,ISessionFactory在每次請求中只會生成一次,當我們需要session的時候只需調用GetCurrentSession方法,當http請求的時候session創建並存儲在CurrentSessionContext.Bind()中,並開啟事物操作,當請求結束的時候 CurrentSessionContext.Unbind()移除session,事物提交並將session關閉。這里存在一些問題:盡管session是非常輕量級的,這樣每一次http請求都會去創建session,並不能做到我們真正需要的時候去創建。
注冊httpmodel:
在web.config中添加如下2處節點:
測試程序
Models:

public class Employee { public virtual int Id { get; protected set; } public virtual string FirstName { get; set; } public virtual string LastName { get; set; } public virtual Store Store { get; set; } } public class Product { public virtual int Id { get; protected set; } public virtual string Name { get; set; } public virtual double Price { get; set; } public virtual IList<Store> StoresStockedIn { get; protected set; } public Product() { StoresStockedIn = new List<Store>(); } } public class Store { public virtual int Id { get; protected set; } public virtual string Name { get; set; } public virtual IList<Product> Products { get; protected set; } public virtual IList<Employee> Staff { get; protected set; } public Store() { Products = new List<Product>(); Staff = new List<Employee>(); } public virtual void AddProduct( Product product ) { product.StoresStockedIn.Add( this ); Products.Add( product ); } public virtual void AddEmployee( Employee employee ) { employee.Store = this; Staff.Add( employee ); } }
Repositories:

public interface IRepository<T> { IQueryable<T> GetAll(); IQueryable<T> Get( Expression<Func<T, bool>> predicate ); IEnumerable<T> SaveOrUpdateAll( params T[] entities ); T SaveOrUpdate( T entity ); } public class Repository<T> : IRepository<T> { private readonly ISession session; public Repository() { session = NHibernateSessionPerRequest.GetCurrentSession(); } public IQueryable<T> GetAll() { return session.Query<T>(); } public IQueryable<T> Get( Expression<Func<T, bool>> predicate ) { return GetAll().Where( predicate ); } public IEnumerable<T> SaveOrUpdateAll( params T[] entities ) { foreach ( var entity in entities ) { session.SaveOrUpdate( entity ); } return entities; } public T SaveOrUpdate( T entity ) { session.SaveOrUpdate( entity ); return entity; } }
HomeController:

public class HomeController : Controller { private readonly IRepository<Store> storeRepository; public HomeController() { storeRepository = new Repository<Store>(); } public ActionResult Index() { var stores = storeRepository.GetAll(); return View(stores.ToList()); } public ActionResult Test() { var barginBasin = storeRepository.Get(s => s.Name == "Bargin Basin").SingleOrDefault(); if (barginBasin == null) { return RedirectToAction("Index"); } barginBasin.Name = "Bargain Basin"; return RedirectToAction("Index"); } public ActionResult Seed() { var barginBasin = new Store { Name = "Bargin Basin" }; var superMart = new Store { Name = "SuperMart" }; var potatoes = new Product { Name = "Potatoes", Price = 3.60 }; var fish = new Product { Name = "Fish", Price = 4.49 }; var milk = new Product { Name = "Milk", Price = 0.79 }; var bread = new Product { Name = "Bread", Price = 1.29 }; var cheese = new Product { Name = "Cheese", Price = 2.10 }; var waffles = new Product { Name = "Waffles", Price = 2.41 }; var daisy = new Employee { FirstName = "Daisy", LastName = "Harrison" }; var jack = new Employee { FirstName = "Jack", LastName = "Torrance" }; var sue = new Employee { FirstName = "Sue", LastName = "Walkters" }; var bill = new Employee { FirstName = "Bill", LastName = "Taft" }; var joan = new Employee { FirstName = "Joan", LastName = "Pope" }; AddProductsToStore(barginBasin, potatoes, fish, milk, bread, cheese); AddProductsToStore(superMart, bread, cheese, waffles); AddEmployeesToStore(barginBasin, daisy, jack, sue); AddEmployeesToStore(superMart, bill, joan); storeRepository.SaveOrUpdateAll(barginBasin, superMart); return RedirectToAction("Index"); } private void AddProductsToStore(Store store, params Product[] products) { foreach (var product in products) { store.AddProduct(product); } } private void AddEmployeesToStore(Store store, params Employee[] employees) { foreach (var employee in employees) { store.AddEmployee(employee); } } }
完成以后,修改hibernate.cfg.xml中的鏈接字符串等,並將其屬性復制到輸出目錄修改為:如果較新則復制,運行程序請求index方法,將會產生數據庫架構。
配置MiniProfiler.NHibernate
1、使用nuget控制台Install-Package MiniProfiler.NHibernate安裝,或者參考github中的代碼自己寫一個
2、修改NH配置文件中的數據庫驅動類,將connection.driver_class幾點替換為
<property name="connection.driver_class"> StackExchange.Profiling.NHibernate.Drivers.MiniProfilerSql2008ClientDriver,StackExchange.Profiling.NHibernate </property>
3、在模板頁中中的<head>節點添加@MiniProfiler.RenderIncludes()方法調用,修改Global.asax,添加如下代碼:
protected void Application_BeginRequest() { if (Request.IsLocal) { MiniProfiler.Start(); } } /// <summary> /// 終止時結束 /// </summary> protected void Application_EndRequest() { MiniProfiler.Stop(); }
配置完成后運行程序,便可以看到MiniProfiler.NHibernate的效果了,使用它可以幫我們監控nh產生的sql及時優化代碼,舉個例子,運行請求home/seed的測試數據方法,看看追蹤的效果
文章結束,時間倉促代碼粗略,文中若有不合理的地方,歡迎批評指正。