[ASP.NET MVC 開發系列] 第一次使用 NHibernate 就上手 (NHibernate Designer 篇)

在 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 架構圖

image

圖片取自 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,稍後提到。如下圖所示:

image

圖片取自 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 ,如下圖:

image

圖片取自 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 下載

image

下載下來的為一個 .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

image

使用預設的 Single File

image

選擇、當我新增 Entity 時自動以 System.Int32 作為 Identity (預設值)

image

Identity 產生方式,先使用 HiLo ,到時候都可以再修改

image

完成後 Designer 的幫助之下,他會自動幫您將 NHibernate、EntityFramework、NHibernate.ByteCode.LinFu、NHibernate.Validator、等相關組件參考進來。

 

步驟(四)、從伺服器總管拖曳一個 Customers 資料表到 NHibernate Designer 設計畫面

如下圖直接拖曳即可,它的設計畫面與 Entity Framework 幾乎非常的類似,很容易上手。下一次筆者在介紹較複雜的應用。

image

這裡我們需要做點設定,由於 CustomerID 並不是自動產生的,所以我們要將它的 Identity Generator 設為 "Assigned",如下:

image

 

步驟(五)、透過 NHibernate Designer 設定 config (連線字串)

以往使用 NHibernate 一定是自行設定 config (NHibernate.Cfg.ConfigurationSectionHandler) ,透過工具只要按一個建便能自動幫您設定好,且工具會自動判別如果是 Web 專案就設定在 web.config、若是WinForm就設定在 app.config。使用方式點選滑鼠右鍵,選擇 Get Started,如下圖:

image

在點選了 Get Started 之後,會出現一個 Get Started With MHibernate 畫面,在這個畫面中您可以設定 config 檔案,以及參考呼叫 SessionFactory 的範例程式碼。如下:

image

 

步驟(六)、建立 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 程式碼,設計時也才可以使用模型繫結等功能。為了方便起見我們可以使用類別圖表來產生。順便只鍵入我們想要顯示的欄位,筆者任意鍵一個如下:

image

他幫我們產生如下類別框架:

   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,如下圖:

image

並在 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:          }

程是這時候已經可以執行了,如果沒有任意外,執行結果如下:

image

 

步驟(十)、實作 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 時,執行結果會是如下,並輸入一些資料,如下面畫面:

image

點選 Create 按鈕後,會呼叫 HttpPost 的那一個 Controller 方法,由於模型繫結的機制會自動將 VMCustomer 以參數方式傳遞進來,我們只需要產生一個 Customer 的值型個體後,呼叫 SessionFactory的 Save 方法將資料存入後,並回到 Index 頁面。

並回到 Index 頁面後就可以看見我們剛剛新增的那筆資料就在最上方,如下圖:

image

 

步驟(十一)、建立 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 按鈕時會實際刪除該筆資料。

image

 

步驟(十二)、建立 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 是不是非常容易入門呢 :-)

 

本文範例程式:

MvcNHibernateCustomerApp1

 

相關參考資料:

NHibernate Reference (包含PDF、CHM及HTML三種格式) 此資料最完整

http://sourceforge.net/projects/nhibernate/files/NHibernate/2.1.2GA/NHibernate-2.1.2.GA-reference.zip/download

NHibernate Designer

http://www.mindscapehq.com/products/nhdesigner

NHibernate (官方網站/下載)

http://nhforge.org/

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/


 

如果文章對您有用,幫我點一下讚,或是點一下『我要推薦,這會讓我更有動力的為各位讀者撰寫下一篇文章。

非常謝謝各位的支持與愛護,小弟在此位各位說聲謝謝!!! ^_^