在 ASP.NET MVC 漸漸開始熱門起來的時候,感覺似乎也帶起一些學習ORM的熱潮。難道開發 ASP.NET MVC 就一定要用 Entity Framework 或 LINQ to SQL嗎?當然並不是這樣,仿間光是 Open Sources 的 ORM 產品大約就有 35 種左右..
前言
在 ASP.NET MVC 漸漸開始熱門起來的時候,感覺似乎也帶起一些學習ORM的熱潮。因為有許多人會告訴你,View 的資料來自於 Model,Controller只是協調而已,而View要呈現資料,勢必最好定義個 ViewModel 以便於資料的呈現,在 IDE 工具上的支援程度也比較高。比如說微軟自家的 Entity Framework 或LINQ to SQL 大部分自然會是設計 ASP.NET MVC 的首選。之前就有人質疑了,難道開發 ASP.NET MVC 就一定要用 Entity Framework 或 LINQ to SQL嗎?當然並不是這樣,仿間光是 Open Sources 的 ORM 產品大約就有 35 種左右,只要是ORM一定可以使用在 ASP.NET MVC ,更正確的說,只要你可以存取資料庫 (Model),你可以將取回來的資料對應致 View 上都可以,哪怕是自己實作 Mapping Class & ViewModel 都行!其實早在2004年左右,筆者任職的公司就是自行 Hot Code 撰寫一個 Entity 的 Mapping 機制來處理畫面的資料與 Remoting 遠端物件的封送處理。所以只要道理通了,想要如何實作都不是難事。
	
	為什麼選擇 NHibernate
這真的是筆者從出道以來,第一次使用 NHibernate,在筆者曾經在.NET 平台使用 LLBGen 時,就聽聞 Java 平台的 Hibernate ,聽說很好用。因為每次隔壁的 Java Team 的在面試新人時總是會問有沒有用過 Hibernate (Java平台叫 Hibernate 、.NET平台的稱作 NHibernate)。當然我是從來沒機會接觸XD,專案中也從來未使用過。我覺得有趣的是,這個星期天左右,我在點部落看了 黃偉榮 在 ASP.NET MVC 關於NHibernate 的一系列教學,我頓時突然間對 NHibernate 非常的感興趣,除了找尋所有 Open Sources 的 ORM 產品之外,也在測試哪一種提供給 PG 較恰當的 ORM 工具。而後來發現以 Open Sources 來講,還是 NHibernate 較為完整,在 NHibernate 的官方網站不僅有完整的 Documents 還有幾乎等於電子書的PDF檔,喔喔~~好棒。
NHibernate 有沒有視覺化的工具呢?
在測試的過程中,筆者又發現 NHibernate 其實也有 for Visual Studio 2010 的視覺化工具,而會找這樣的工具因為讓開發人員花過多的時間處理 Mapping Class 的細節有一點違背當初希望開發人員注重在 Business Logic 設計的本意 (當然有機會我還是希望他們練習Mapping Class的實作),所以筆者另外Google一下看看 NHibernate 是否有像 Entity Framework 一樣的視覺化工具。很幸運的,我一下就找到了^_^
NHibernate 的 Architecture 架構圖
圖片取自 NHibernate Reference (http://sourceforge.net/projects/nhibernate/files/NHibernate/2.1.2GA/NHibernate-2.1.2.GA-reference.zip/download)
在 NHibernate 的架構中,當然相同的應用程式只需要知道 Persistent Objects 有什麼東西即可,不需要理會Persistent Objects 後端的實作是什麼,這也是ORM的初衷之一。而在 NHibernate 當中 Persistent Objects 需要透過 Session 來提供服務,當然這得依靠 Session Factory,稍後提到。如下圖所示:
圖片取自 NHibernate Reference (http://sourceforge.net/projects/nhibernate/files/NHibernate/2.1.2GA/NHibernate-2.1.2.GA-reference.zip/download)
而實際存取資料庫的通常是 ADO.NET 或 OLEDB 或者是 ODBC,而與資料庫進行互動的部分交由 Session 來統籌處理,這裡的 Session 指的並不是 Web 架構下的 Session ,指的是與資料庫進行互動的 Session 。核心的部分的實作為 SessionFactory ,在他的下面包括了 Transaction Factory & Connection Provider ,如下圖:
圖片取自 NHibernate Reference (http://sourceforge.net/projects/nhibernate/files/NHibernate/2.1.2GA/NHibernate-2.1.2.GA-reference.zip/download)
開始實作
在了解了 NHibernate 的基本架構之後,當然就是實做一遍了。初次筆者先不探討 NHibernate 繼承的策略與 Mapping 的細節,也因為有曾經有人在詢問 NHibernate 是否有像 Entity Framework 視覺化的設計工具。其實是有的,就是前面提到的 NHibernate Designer 。因此在開發之前我們必須安裝 NHibernate Designer 。那麼就先照著筆者下面幾個步驟進行吧 :-)
步驟(一)、下載 NHibernate Designer
首先到 http://www.mindscapehq.com/products/nhdesigner 下載
下載下來的為一個 .VSIX 檔案,直接執行安裝後重新啟動 Visual Studio 2010 即可!
當然,您也可以由擴充管理員進行安裝。此安裝會自動將 NHibernate SDK 相關組件安裝起來,不需要在另外下載NHibernate Sources。
不過目前 NHibernate Designer 的免費版本只支援 10 以內的 Entity.
步驟(二)、建立 ASP.NET MVC 4 專案
當然,這個例子不一定要用 ASP.NET MVC 4 來建立,使用 ASP.NET MVC 3也可以。首先建立一個名為 "MvcNHibernateCustomerApp1" 的專案,因為我們將以 Northwind 資料庫的 Customers 資料表為例,一步步建立一個 CRUD 的應用程式
注意:請建立 Empty 的 MVC 專案。
步驟(三)、加入視覺化的 NHibernate Model
在安裝完成 NHibernate Designer 後,在專案加入 NHibernate Model
使用預設的 Single File
選擇、當我新增 Entity 時自動以 System.Int32 作為 Identity (預設值)
Identity 產生方式,先使用 HiLo ,到時候都可以再修改
完成後 Designer 的幫助之下,他會自動幫您將 NHibernate、EntityFramework、NHibernate.ByteCode.LinFu、NHibernate.Validator、等相關組件參考進來。
步驟(四)、從伺服器總管拖曳一個 Customers 資料表到 NHibernate Designer 設計畫面
如下圖直接拖曳即可,它的設計畫面與 Entity Framework 幾乎非常的類似,很容易上手。下一次筆者在介紹較複雜的應用。
這裡我們需要做點設定,由於 CustomerID 並不是自動產生的,所以我們要將它的 Identity Generator 設為 "Assigned",如下:
步驟(五)、透過 NHibernate Designer 設定 config (連線字串)
以往使用 NHibernate 一定是自行設定 config (NHibernate.Cfg.ConfigurationSectionHandler) ,透過工具只要按一個建便能自動幫您設定好,且工具會自動判別如果是 Web 專案就設定在 web.config、若是WinForm就設定在 app.config。使用方式點選滑鼠右鍵,選擇 Get Started,如下圖:
在點選了 Get Started 之後,會出現一個 Get Started With MHibernate 畫面,在這個畫面中您可以設定 config 檔案,以及參考呼叫 SessionFactory 的範例程式碼。如下:
步驟(六)、建立 NHibernateHelper 類別
要正確的使用 NHibernate 的 Session 執行階段,必須呼叫使用 SessionFactory ,我們先將 Get Started 所提供的範例程式碼複製起來,並在Models的資料夾建立一個 NHibernateHelper 類別。如下:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Web;
5: using NHibernate;
   6:  
	7: namespace MvcNHibernateCustomerApp1.Models.Helper
   8:  {
	9: public static class NHibernateHelper
  10:      {
	11: private static ISessionFactory _sessionFactory;
  12:  
	13: internal static ISessionFactory SessionFactory
  14:          {
	
  15:              get
	
  16:              {
	17: if (_sessionFactory == null)
  18:                  {
	
  19:                      var configuration = ConfigurationHelper.CreateConfiguration();
	
  20:                      _sessionFactory = configuration.BuildSessionFactory();
	
  21:                  }
	22: return _sessionFactory;
  23:              }
	
  24:          }
	
  25:  
	26: public static ISession OpenSession()
  27:          {
	28: return SessionFactory.OpenSession();
  29:          }
	
  30:      }
	
  31:  }
步驟(七)、建立 ViewModels
在建立 View 時我們必須建立所需要的 Model。而後透過 Visual Studio 2010 也比較方便幫我產生基本的 View 程式碼,設計時也才可以使用模型繫結等功能。為了方便起見我們可以使用類別圖表來產生。順便只鍵入我們想要顯示的欄位,筆者任意鍵一個如下:
他幫我們產生如下類別框架:
1: public class VMCustomer
   2:      {
	3: public string CustomerId
   4:          { get; set; }
	
   5:  
	6: public string CompanyName
   7:          { get; set; }
	
   8:  
	9: public string ContactName
  10:          { get; set; }
	
  11:  
	12: public string ContactTitle
  13:          { get; set; }
	
  14:  
	15: public string Phone
  16:          { get; set; }
	
  17:  
	18: public string Fax
  19:          { get; set; }
	
  20:  
	21: public string Country
  22:          { get; set; }
	
  23:  
	24: public string City
  25:          { get; set; }
	
  26:  
	27: public string Address
  28:          { get; set; }
	
  29:      }
這裡筆者也是透過 Modeling PowerToys 工具修飾為 {get; set;} ,這樣看起來比較清爽。可以到 http://modeling.codeplex.com/ 下載。
步驟(八)、建立 Controller
由於在專案預設的 RouteMap 裡 ControllerName 就等於 "Home",那麼我們也就不修改了,就建立一個 Empty 的 Controller 吧。接著在該 Controller 按滑鼠右鍵 "Add View" 進入到下面步驟(九)
步驟(九)、建立 Index 主畫面,並撰寫撈資料的程式碼
同樣的預設的 RouteMap 裡的 action 就是 Index ,我們就建立一個 Index 的 View 吧。這裡我們要建立的是 Strontly-typed 的 View,請將 Model Class 設為剛剛建立的 VMCustomer,Scaffold template 請選擇 List,如下圖:
並在 Controller 加入如下程式碼:
1: public ActionResult Index()
   2:          {
	
   3:              ISession context = NHibernateHelper.OpenSession();
	4: try
   5:              {
	6: IQuery query = context.CreateQuery("FROM Customer");
   7:                  IList<Customer> customerList = query.List<Customer>();
	8: List<VMCustomer> vmCusList = new List<VMCustomer>();
9: foreach (var customer in customerList)
  10:                  {
	11: VMCustomer vmCustomer = new VMCustomer()
  12:                      {
	
  13:                          CustomerId = customer.CustomerId,
	
  14:                          CompanyName = customer.CompanyName,
	
  15:                          ContactName = customer.ContactName,
	
  16:                          ContactTitle = customer.ContactTitle,
	
  17:                          Country = customer.Country,
	
  18:                          Phone = customer.Phone,
	
  19:                          Fax = customer.Fax,
	
  20:                          Address = customer.Address
	
  21:                      };
	
  22:                      vmCusList.Add(vmCustomer);
	
  23:                  }
	24: return View(vmCusList);
  25:              }
	26: finally
  27:              {
	
  28:                  context.Close();
	
  29:              }
	
  30:          }
程是這時候已經可以執行了,如果沒有任意外,執行結果如下:
步驟(十)、實作 Create New 的程式碼
如果到這裡沒有任何問題就開始進行下一步驟,建立 Create New 的部分。完整的 CRUD 應用,新增資料也算最重要的一環,這邊我們會看到 NHibernate 如何處理交易,在這個例子中所使用到的方式其實不難,因為目前只有一個 Table 而已,那麼就開始吧 :)
在 Create New 這裡,進入新增 View 的 action 名稱是 "Create",但我們需要兩個方法,一個是回傳空的 ActionResult (因為新增是另一個畫面 View)、另一個實際新增的方法,須使用 [AcceptVerbs(HttpVerbs.Post)] 的 attribute 區隔開來。筆者先撰寫程式碼如下:
1: public ActionResult Create()
   2:          {
	3: return View();
   4:          }
	
   5:  
	
   6:          [AcceptVerbs(HttpVerbs.Post)]
	7: public ActionResult Create(VMCustomer vmCustomer)
   8:          {
	
   9:              ISession context = NHibernateHelper.OpenSession();
	10: ITransaction trans = null;
11: try
  12:              {
	
  13:                  trans = context.BeginTransaction();
	14: Customer customer = new Customer() {
15: CustomerId = context.CreateQuery("FROM Customer").List<Customer>().Count.ToString(),
  16:                      CompanyName = vmCustomer.CompanyName,
	
  17:                      ContactName = vmCustomer.ContactName,
	
  18:                      ContactTitle = vmCustomer.ContactTitle,
	
  19:                      Country = vmCustomer.Country,
	
  20:                      City = vmCustomer.City,
	
  21:                      Phone = vmCustomer.Phone,
	
  22:                      Fax = vmCustomer.Fax,
	
  23:                      Address = vmCustomer.Address
	
  24:                  };
	
  25:                  context.Save(customer);
	
  26:                  trans.Commit();
	27: return RedirectToAction("Index");
  28:              }
	29: catch (Exception ex)
  30:              {
	31: throw ex;
  32:              }
	33: finally
  34:              {
	
  35:                  context.Close();
	
  36:              }
	
  37:          }
如上程式,會使用到 NHibernate 的 HQL 查詢語法 (這是 NHibernate 自己創造的對於 ORM 物件的查詢與法,與筆者之前介紹的 ECO 的 OCL 是相同的東西,有興趣可參考 MHibernate Reference),程式中的 CustomerID 我先給他一個總筆數的 COUNT,接著使用 步驟(九) 的方式加入 Create 的 View,這時程式執行時,點選 Create New 時,執行結果會是如下,並輸入一些資料,如下面畫面:
點選 Create 按鈕後,會呼叫 HttpPost 的那一個 Controller 方法,由於模型繫結的機制會自動將 VMCustomer 以參數方式傳遞進來,我們只需要產生一個 Customer 的值型個體後,呼叫 SessionFactory的 Save 方法將資料存入後,並回到 Index 頁面。
並回到 Index 頁面後就可以看見我們剛剛新增的那筆資料就在最上方,如下圖:
步驟(十一)、建立 Delete 的程式碼
前面的步驟如果都沒有問題的話,我們現在來試試刪除的部分。同樣的,先建立一個 Delete 的 View ,同樣的,在預設的範本中會先秀出該筆資料讓使用者確認是否要刪除。因此需要一個帶出一筆資料的方法與實際刪除資料的方法。那麼我們在 SD 階段的時候應該會發現取得單筆資料與我們之後要撰寫的 Edit & Detail 的 View 中應該會有共用的部分,因此筆者在撰寫程式時,除了 Delete 的 Get & POST 方法之外,另外會將實際使用 HQL 語法的部分拆至 GetOneCustomer 方法中,各個取得單一筆 VMCustomer 的部分拆出一個 GetOneVMCustomerForActionResult。此時筆者 Delete 的 Controller 程式碼如下:
1: /// <summary>
2: /// 使用當前的 db session ,用 id 取得一筆資料
3: /// </summary>
4: /// <param name="id">Pk</param>
5: /// <param name="context">DB Session for Context</param>
6: /// <returns>MvcNHibernateCustomerApp1.Customer</returns>
7: Customer GetOneCustomer(string id, ISession context)
   8:          {
	
   9:              IQuery query = context.CreateQuery(
	10: "FROM Customer C WHERE C.CustomerId=:CustomerId")
11: .SetParameter<string>("CustomerId", id);
  12:              IList<Customer> customerList = query.List<Customer>();
	13: if (customerList.Count <= 0)
14: return null;
15: return customerList[0];
  16:          }
	17: /// <summary>
18: /// 用 id 取得一筆資料
19: /// </summary>
20: /// <param name="id">Pk</param>
21: /// <returns>MvcNHibernateCustomerApp1.Customer</returns>
22: Customer GetOneCustomer(string id)
  23:          {
	
  24:              ISession context = NHibernateHelper.OpenSession();
	25: try
  26:              {
	27: return GetOneCustomer(id, context);
  28:              }
	29: finally
  30:              {
	
  31:                  context.Close();
	
  32:              }
	
  33:          }
	34: /// <summary>
35: /// 用 id 取得一筆 VMCustomer 的 ActionResult
36: /// </summary>
37: /// <param name="id">Pk</param>
38: /// <returns></returns>
39: ActionResult GetOneVMCustomerForActionResult(string id)
  40:          {
	
  41:              Customer c = GetOneCustomer(id);
	42: return View(new VMCustomer()
  43:              {
	
  44:                  CustomerId = c.CustomerId,
	
  45:                  CompanyName = c.CompanyName,
	
  46:                  ContactName = c.ContactName,
	
  47:                  ContactTitle = c.ContactTitle,
	
  48:                  Country = c.Country,
	
  49:                  City = c.City,
	
  50:                  Fax = c.Fax,
	
  51:                  Phone = c.Phone,
	
  52:                  Address = c.Address
	
  53:              });
	
  54:          }
	
  55:  
	56: public ActionResult Delete(string id)
  57:          {
	58: return GetOneVMCustomerForActionResult(id);
  59:          }
	
  60:  
	
  61:          [AcceptVerbs(HttpVerbs.Post)]
	62: public ActionResult Delete(string id, FormCollection form)
  63:          {
	
  64:              ISession context = NHibernateHelper.OpenSession();
	65: ITransaction trans = null;
66: try
  67:              {
	
  68:                  trans = context.BeginTransaction();
	
  69:                  Customer oneCustomer = GetOneCustomer(id, context);
	70: if (oneCustomer!=null)
  71:                  {
	
  72:                      context.Delete(oneCustomer);
	
  73:                  }
	
  74:                  trans.Commit();
	75: return RedirectToAction("Index");
  76:              }
	77: finally
  78:              {
	
  79:                  context.Close();
	
  80:              }
	
  81:          }
如程式中 GetOneCustomer(id, ISession) 之中,在 HQL 的查詢語句裡我們使用了 SessionFacctory 提供的 SetParameter 方法加入參數以避免SQL Injection 問題,這裡 NHibernate 也是非常的貼心的。而 NHibernate 其實最近也支援 LINQ,這部分下一次有機會再來談。如程式中在實際 Delete 方法之後一樣使用 RedirectToAction 回到 Index 。這時我們可以試著來執行一下,當點選 Delete 時會帶入 Delete 的 View ,當點選 Delete 按鈕時會實際刪除該筆資料。
步驟(十二)、建立 Edit & Details 的 Controller 程式碼
在前面的步驟中我們已經完成大部分的程式碼了,只剩下 Edit 會更新資料庫而已。而剛才共用的部分已經切出來,所以現在 Edit & Details 的 Controller 就使用 GetOneVMCustomerForActionResult 方法來取得單一筆的資料,筆者先撰寫 Edit 的程式碼,這部分因為這裡不是透 ClassMapping 方式來處理,所以 Edit 先使用比較笨的方法,一個將值設給 Customer,最後 Save 到資料庫中。因此Edit & Details 程式碼筆者就一次撰寫完成,因為這已是最後的部份了。程式碼如下:
1: public ActionResult Edit(string id)
   2:          {
	3: return GetOneVMCustomerForActionResult(id);
   4:          }
	
   5:  
	
   6:          [AcceptVerbs(HttpVerbs.Post)]
	7: public ActionResult Edit(VMCustomer vmCustomer)
   8:          {
	
   9:              ISession context = NHibernateHelper.OpenSession();
	10: ITransaction trans = null;
11: try
  12:              {
	
  13:                  trans = context.BeginTransaction();
	
  14:                  Customer oneCustomer = GetOneCustomer(vmCustomer.CustomerId, context);
	15: if (oneCustomer != null)
  16:                  {
	
  17:                      oneCustomer.CompanyName = vmCustomer.CompanyName;
	
  18:                      oneCustomer.ContactName = vmCustomer.ContactName;
	
  19:                      oneCustomer.ContactTitle = vmCustomer.ContactTitle;
	
  20:                      oneCustomer.Country = vmCustomer.Country;
	
  21:                      oneCustomer.City = vmCustomer.City;
	
  22:                      oneCustomer.Address = vmCustomer.Address;
	
  23:                      oneCustomer.Phone = vmCustomer.Phone;
	
  24:                      oneCustomer.Fax = vmCustomer.Fax;
	
  25:                      context.Save(oneCustomer);
	
  26:                  }
	
  27:                  trans.Commit();
	28: return RedirectToAction("Index");
  29:              }
	30: finally
  31:              {
	
  32:                  context.Close();
	
  33:              }
	
  34:          }
	
  35:  
	36: public ActionResult Details(string id)
  37:          {
	38: return GetOneVMCustomerForActionResult(id);
  39:          }
Ok! 到這裡整個 CRUD 都已經完成,Edit & Details 帶入View 的部分都共用 GetOneVMCustomerForActionResult 方法是不是看起來較為清爽呢:)
後記:
筆者由上星期天第一次接觸 NHibernate 到現在寫這篇文章不過3-4天而已,如何? 這個 NHibernate Designer 是不是非常容易入門呢 :-)
本文範例程式:
相關參考資料:
NHibernate Reference (包含PDF、CHM及HTML三種格式) 此資料最完整
NHibernate Designer
http://www.mindscapehq.com/products/nhdesigner
NHibernate (官方網站/下載)
NHibernate (Getting Started with the NHibernate Designer)
http://www.mindscapehq.com/products/nhdesigner/getting-started
NHibernate - Learning with Code Samples
http://www.fincher.org/tips/Languages/NHibernate.shtml
NHibernate How to
http://nhforge.org/wikis/howtonh/default.aspx
 
簽名:
學習是一趟奇妙的旅程
這當中,有辛苦、有心酸、也有成果。有時也會有瓶頸。要能夠繼續勇往直前就必須保有一顆最熱誠的心。
軟體開發之路(FB 社團):https://www.facebook.com/groups/361804473860062/
Gelis 程式設計訓練營(粉絲團):https://www.facebook.com/gelis.dev.learning/
如果文章對您有用,幫我點一下讚,或是點一下『我要推薦』,這會讓我更有動力的為各位讀者撰寫下一篇文章。
非常謝謝各位的支持與愛護,小弟在此位各位說聲謝謝!!! ^_^
            







