在 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/
如果文章對您有用,幫我點一下讚,或是點一下『我要推薦』,這會讓我更有動力的為各位讀者撰寫下一篇文章。
非常謝謝各位的支持與愛護,小弟在此位各位說聲謝謝!!! ^_^