NHibernate 變的簡單
前言
這篇文章出自于我嘗試學習使用Nhiberbnate的挫敗感。我發現好像Nhibernate全部的介紹材料不是很模糊就是太詳細。我所需要的就是一個簡單直接的教程,能讓我盡快對NHibernate熟悉起來。我從來沒有找到。幸運的是,這篇文章將會滿足別人的這些需求。
這篇文章有些長,但是我鼓勵你以你的方式來閱讀。NHibernate是一個復雜的程序,是一個綿延曲折的學習過程。這篇文章將為你踏平曲折,從幾天或是幾周縮短到幾個小時。
問題
NHibernate目的是解決一個眾所周知的問題,對象持久代碼在開發過程中的瓶頸問題。很多文章表明:1/4到1/3的程序代碼是關于對象持久化,從數據庫中讀取數據,和將數據寫回數據庫。代碼是重復的,耗費時間的,還有很多瑣碎的代碼要寫。
對于這個問題,有很多解決方案是可用的。代碼生成可以在幾秒鐘生成數據訪問代碼。但是如果業務模型改變,這些代碼需要重新生成。"對象關系映射"(ORMs)使用了一種新的方式,像NHibernate。他們管理數據訪問更加透明,提供了很多簡潔的API,可以使用一輛行代碼來實現加載和保存整個對象。
介紹NHibernate
NHibernate是一個持久化引擎框架。它從數據庫中加載業務對象,以及將這些對象的變化更新到數據庫中。從文章上面可以看出,它可以只使用一兩行代碼實現業務對象的加載和保存。
NHibernate使用映射文件來引導從數據庫數據到業務對象的轉換。還有一種方法,你可以使用類的特性和屬性來替代映射文件。為了讓事情盡量簡單,我們在這篇文章中將使用映射文件,而不是使用類特性。另外,映射文件能夠很清晰的將業務邏輯和持久化代碼分開。
好了,我們只需要在程序中添加幾行代碼,和為每一個映射文件創建持久化類,而且NHibernate可以照顧到所有的數據庫操作。真不知道使用NHibernate將為我們節省多少開發時間。
記住在.NET環境下,NHibernate并不是唯一的ORM框架。有許多商業的和開源的產品可以提供這樣的服務。NHibernate是其中最流行的,主要是因為他遺傳自強大的Hibernate,一個Java環境下非常流行的ORM框架。另外,微軟也為ADO.NET提供了"Entity Framework",來提供ORM服務。但是,這個產品已經延遲,好長時間已經沒有再釋放了。
安裝NHibernate
使用NHibernate的第一步就是下載NHibernate和Log4Net(一個開源的日志記錄程序,NHibernate使用它來記錄錯誤和警告),NHibernate包含了Log4Net最新的版本,你也可以下載整個Log4Net安裝包,這里是下載地址:
NHibernate不是直接需要Log4Net,可是在調試期間它的自動記錄日志功能非常有用。
現在開始
在這篇文章中,我將使用一個簡單的示例程序,而不是解說如何使用NHibernate來進行數據訪問。這是一個控制臺應用程序,通過消除UI代碼讓程序變的更加簡單。這個程序將創建很多個業務對象,來使用NHibernate來對他們進行持久化,然后將他們從數據庫中讀取出來。
為了程序運行起來,你需要做一下幾個事情:
- 為陳旭添加NHibernate和Log4Net的程序集引用
- 為程序添加數據庫
- 修改數據庫連接字符串
這個示例程序引用NHibernate和Log4Net。這些應用應該被你的機器識別,如果你的NHibernate和log4net安裝在默認的目錄里。如果這些引用不被識別,你可以分別使用NHibernate.dll和Log4Net.dll來替換引用位置。這些DLL文件可以在NHibernate的安裝目錄中找到。
這個示例程序是按照SQL Server Express 2005來配置的,數據庫文件(NhibernateSimpleDemo.mdf和NhibernateSimpleDemo.ldf)已經打包在壓縮文件里。你可以將數據庫搭在你機器的SQL Server上。
最后,數據庫的連接字符串配置在App.config文件中,默認你使用的是SQL Server數據庫。你可以自己根據自己機器SQL Server的版本,來修改數據庫的連接字符串。
業務模型
這里有兩種方法使用NHibernate創建應用程序。第一種是“以數據為中心”的方法,它從數據模型和創建業務對象開始。第二種是“以對象為中心”的方法,從業務模型和創建數據庫來持久化這個模型開始。這個示例程序使用以對象為中心的方式。
這里示例中的業務模型:
這個模型表現了一個訂單系統的框架,這個模型是不完整的,這里只是使用了幾個類來解說使用NHibernate對象持久化。顯然這個模型的設計不能代表最佳實踐,但是用來展示NHibernate是怎樣工作的已經足夠應付了。
此文將使用這個模型來解說使用NHibernate進行對象持久化的幾個概念。
- 處理簡單屬性
- 處理Components
- 處理one-to-many
- 處理many-to-one
- 處理many-to-many
此文不會涉及高級主題,像繼承。
這個模型由5個類組成,其中的4個是要持久化的類,非持久化類OraderSystem當做這個對象模型的宿主。我們將會在程序運行時初始化OrderSystem對象,然后我們將加載其他的對象到OrderSystem中來。
OrderSystem.Customers屬性擁有銷售者的客戶列表,Customers可以通過CustomerID來訪問,每一個Customer對象擁有一個ID,name,和address、一個序列的orders。address將被包成一個單獨的Address類中。
Order類包含了一個訂單的ID,時間,顧客信息,和許多購買的產品信息。
Product類包含ID,名稱。
請注意我們只注重NHibernate是怎么工作的,程序初始化時,Product對象將被實例化放入OrderSystem.Catalog屬性中,當一個訂單被創建時,Product對象引用將不復制到Order.OrderItems屬性中。
NHibernate一個強大的特點就是不需要為業務類實現特別的接口。事實上,業務對象通常不會擔心被持久化機制來加載和保存他們。NHibernate使用的映射數據保存在分離的XML文件中。
數據庫
數據庫和對象模型并不是完全匹配,對象模型中包含一個Address類,但是數據庫中并沒有與之對應的表。數據庫中有OrderItems表,但是對象模型中并沒有與之對應的持久化的類。這里的不匹配并不是故意的,我們想展示的NHibernate其中的一個概念就是這里并不需要數據庫中的表跟類是一一對應的。
這里是為什么不完全匹配的原因:
- Address類并不能代表業務模型的一個實體,相反,它只代表一個實體的值,在這個示例中,代表Customer.Address 屬性,我們將Address分離一個單獨的類,這樣我們可以解說什么叫NHibernate使用“Component mapping”。
- OrderItems表是多對多關系中Orders和Products的連接表,這樣也不能代表對象模型中的一個實體。
Customer表包含了一個普通Customer信息的骨架,包含Customer的Address信息。最佳實踐不會像我們這樣,將會把Address分離在一個單獨的表中。我們把Address信息保存在Customer表中,這樣我們可以解釋什么是NHibernate使用‘Components’類,而不使用Address自己的表,我們將在下面討論Components的詳細用法。
Orders表只包含一個訂單的最簡單的信息,只有ID,時間,和CustomerID。Orders和Customers之間的關系通過一個外鍵orders.ustomerID 列對應 Customer.ID列。
所有的訂單學要一個多對多的關系(每一條訂單包含很多條商品信息,每一條商品信息又被包含在很多訂單里),所以我們需要OrderItems表來當作中介,簡單的連接Order編號和Product編號。
這個數據庫并不是一個最佳實踐,它所包含的信息僅僅用來展示NHibernate是怎樣工作的。
映射業務模型
許多介紹NHibernate的文章都是以配置文件開始的,但是從另一個地方開始:映射類。映射是NHibernate的核心,而且配置也給初學者一個很大的絆腳石。當我們討論完映射,我們再回來介紹NHibernate的配置部分。
映射簡單的指定哪一個表對應哪一個類。我們把映射的類對應的表叫這個類的“映射表”。
我們在上面就已經說了,Nhibernate不需要特定的接口或是特定的代碼寫在要映射的類里。但是它需要被聲明成Virtual,這樣可以在需要的時候創建代理。NHibernate文檔里討論了為什么這樣,現在我們把所有業務模型中的類聲明成Virtual。
映射可以同過分離的XML文件實現,也可以通過在類屬性上添加特性來實現。被用于映射的XML文件可以在任何一個項目里引用。為了簡單,我們將展示其中一個方法:通過XML文件映射,映射文件需要被編譯成程序集的嵌入式資源。
你可以映射很多類在同一個映射文件中,但是通常都會為每一個類創建一個映射文件。這樣可以保持映射文件的短小,而且容易閱讀。
開始我們映射實驗之前,先讓我們看看Customer.hbm.xml文件。hbm.xml后綴的文件是NHibernate標準的映射文件的后綴。我們把映射文件放在Model文件下,但是我們可以把他們放在項目的任何一個地方。最關鍵的一點是將文件的 BuildAction 屬性設置為 Embedden Resource(嵌入式資源)。這個設置將會把映射文件編譯到程序集里,這樣把他們從程序中脫離出來。
映射文件都是標準的格式:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="NHibernateSimpleDemo"
assembly="NHibernateSimpleDemo">
第一個標簽是XML聲明,第二個標簽定義了XML的命名空間,你可以在這里加入一個XSD信息。第二個標簽還包含了屬性信息,定義了映射文件要被使用在的程序集名稱,這樣可以讓我們避免在映射標簽里寫所有的類名。
<class>標簽
下一個標簽用來識別我們需要映射的類:
<class name="Customer" table="Customers" lazy="false">
<class>標簽的屬性指定了要映射的類,以及它的映射表:
- name屬性指定要映射的類名
- table屬性指定類的映射表
- lazy屬性告知NHibernate對這個類不用使用'lazy loading'
'lazy loading' 告知NHibernate不要從數據庫表中加載這個類對象,直到程序中需要訪問這個對象的數據時才加載。這種方式可以減少一個業務對象的內存占用,從而提高性能。處于簡單,我們將在這個示例程序中不使用lazy loading。但是在以后使用NHibernate做項目的時候,一直到早點知道它的來龍去脈。
<class>標簽中還有很多屬性,可以查閱NHibernate的幫助文檔。
<id>標簽
我們已經標識了類和類對應的映射表,接下來我們需要標識類的標識屬性和映射表中的標識列。當我們創建數據的時候,我們需要標識CustomerID為主鍵(primary key)。
所以要這些:
- 指定Customer類的標識屬性
- 指定Customer表記錄的標識列
- 告知NHibernate讓SQL Server設置CustomerID列的值
<id name="ID">
<column name=" CustomerID " />
<generator class="native" />
</id>
<generator>標簽指定數據庫記錄的標識列的值由SQL Server數據庫原生生成。
簡單屬性
我們設置好類的標識屬性,我們接下來設置類的一般屬性,Customer類有一個簡單屬性:Name。我們把它跟數據庫Customer表Name列匹配,屬性名和列名相同,所以映射文件很簡單:
<property name="Name" />
我們可以匹配Name屬性到一個不同名稱的列上(如CustomerName)。
Component映射
NHibernate使用component來映射那些在數據庫中沒有相應的表來映射的類,就像上面提到的Address類,只是作為Customer的一個屬性值,數據庫中沒有對應的Address表,它遵循一個區別往往出現在"實體類"和"值類"。
- 一個實體類在業務模型中代表著一個實體,在我們的業務模型中,實體類是Customer、Order、和OrderItem。這些類對應著一個業務對象。
- Address類并不代表一個業務對象,它提供了一種封裝類值的方式,在NHibernate的術語里,它是一個Customter對象的一個組件。
NHibernate用'Component'與.NET使用這個詞語無關。一個Component只是一個實體對象的一個值,數據庫中沒有與之映射的表。
<component name="Address">
<property name="StreetAddress" />
<property name="City" />
<property name="State" />
<property name="Zip" />
</component>
<component>標簽是一個集合標簽。NHibernate將使用.NET反射機制來判斷它的真實類型,你也可以通過 class 屬性來指定所對應的類名。Customer.hbm.xml文件將匹配兩個類。
聯合映射
面向對象設計建立在業務模型中的類相互關聯的概念上。
- One-to-one:一個對象至于另外一個對象關聯。像一夫一妻。
- One-to-many:指容器,像一個Customer對象可以包含很多Order對象。
- Many-to-one:許多對象可以指向另外同一個對象,像許多Order對應著一個單獨的Customer對象。
- Many-to-many:許多對象可以指定另一種對象的任何一個。像一個訂單有很多產品,一個產品又可以被包含在很多訂單里。
集合映射:One-To-Many
在Customer類里,你會發現有一個Orders屬性,表示客戶的所有訂單信息。需要注意的一點是:Orders 屬性并不是List<T>類型,而是IList<T>。
private IList<Order> p_Orders = new List<Order>();
// Orders property
public IList<Order> Orders
{
get { return p_Orders; }
set { p_Orders = value; }
}
Orders屬性被聲明為IList<order>,卻被實例化為List<Order>。
這是因為NHibernate需要集合被聲明為接口,而不是接口實現。像上面說的,需要被定義成接口,而不是具體的類。這種被認為是很好的編程實踐。定義為接口,使NHibernate更加靈活,而且能夠提高效率。
NHibernate提供了好幾種標簽來匹配集合。因為當前集合是個IList<T>,現在我們可以使用<bag>來匹配關聯。
<bag name="Orders" cascade="all-delete-orphan" lazy="false">
<key column="CustomerID" />
<one-to-many class="Order" />
</bag>
name屬性可以指定要匹配的類的屬性,cascade屬性的設定表示NHibernate將會操作所有的子對象,包括加載、保存以及刪除。all-delete-orphan表示NHibernate將聯機保存、刪除所有對當前類對應的子對象。
像:當我們刪除Customer這個對象時,Customer下的屬性Orders所有的Order也將會刪除。
在所有的關聯映射中,cascade必須要指定值,否則NHibernate將不會聯機保存或刪除。另外<class>標簽可以指定一個默認的cascade,使用default-cascade。但是這個值只會提供在保存和更新操作上使用聯機操作,不會對刪除也使用聯機操作。所以,最好還是設置cascade這個屬性的值。
跟上面一樣,為了簡單,我們依舊不會使用lazy loading。
<bag>標簽里包含了兩個標簽:
- <key>標簽的column屬性為目標類指定映射表里的列,這一列被用作當前類的映射表和目標類的映射表的外鍵。
- <ont-to-many>標簽表示當前類與以name屬性指定的類之間是一對多的關系。name屬性指定Order類,當前類Customer與Order之間是一對多的關系。
這里我們好像忘記了一點東西,我們指定了目標類,但是沒有指定目標類的映射表。<key>標簽指定了映射表的列的名稱,而不是映射表的名稱。但是NHibernate是怎么知道使用哪個表的呢?這是因為當我們指定好了目標類的名稱之后,NHibernate會通過目標類的XML映射文件來獲取其所應的映射表。所以在這里我們不需要指定目標類的映射表。
現在,我們已經完成了Customer類的映射,我開始轉向Order類。
集合映射:Many-To-One
打開 Order.hbm.xml 文件,現在看的話,里面的內容對你來說已經非常熟悉了。<class>標簽、<property>標簽、還有一個<set>標簽是為了一個訂單和訂單中的商品的一對多關系。但是在Order類和Customer類之間,這里存在著一種新的關系。每一個訂單信息需要知道它是哪個顧客的。
一開始看,這里有點像一對一的關系,一個訂單對應一個顧客,但是這樣是不對的,很多條訂單可以被包含在一個顧客中,盡管一個訂單只對應一個顧客,但是在類的角度上,這是多對一關系。
<many-to-one>標簽適于這中關系。此標簽內不允許有別的標簽,盡管關系是 many-to-one, 'many'個對象同時關聯到一個對象上。它其實跟<property>一樣簡單。
<many-to-one name="Customer"
class="Customer"
column="CustomerID"
cascade="all" />
這里的屬性名稱意思非常直接明了:
- name屬性指定要映射的當前類的屬性,這個示例中,Customer是Order類的一個屬性。
- class屬性指定目標類。這個示例中,目標類就是Customer。
- column屬性指定當前類的映射表與目標類用作外鍵的列。這個示例中,CustomerID是Order與Customer的使用的外鍵。
- cascade屬性指定這種關系的級聯類型。
class屬性不是必需的,NHibernate可以通過.NET反射機制來判斷這個類的真實類型;如果映射表中此列于這個類的屬性名相同,column屬性可以省略。
集合映射:Many-To-Many
在Order.hbm.xml文件中最后一種有趣的關系就是映射OrderItems屬性。OrderItems是IList<Product>類型的,包含了一個序列的Product對象。
這種關系跟One-To-Many關系差不多,因為一個訂單信息包含很多商品信息。但是一種商品信息可以被包含在很多訂單信息里,所以這里是Many-To-Many關系。
在數據庫中,我們使用 OrderItems 表當作 Orders 表和 Products 表的連接表。OrderItems 表中包含Order編號和Product編號。這種方式通常表示多對多的關系。這種方式是單向的多對多關系,是Order類對Product類。雙向的多對多關系是非常復雜的,在本文中我們不會涉及。
那我們怎樣映射多對多關系呢?其實與映射一對多的關系大致相同,我們可以使用<bag>標簽,它包含一個<many-to-many>標簽。
<bag name="Orders" table="OrderItems" cascade="none" lazy="false">
<key column ="OrderID" />
<many-to-many class="Product" column="ProductID" />
</bag>
解釋一下:
- <bag>標簽中的name屬性用來指明要映射的 Customer 類的屬性,這個示例中,就是Orders屬性。
- <bag>標簽中的table屬性用來指明連接表。這里示例中,就是指 OrderItems 表。
- <bag>標簽中的cascade屬性用來指明這種多對多關系的聯機類型。
- <bag>標簽中的lazy屬性用來指明是否使用lazy loading特性。
- <key>標簽用來指明連接表與當前類的映射表實現連接的外鍵。這里是指OrderID。
- <many-to-many>標簽中的class屬性用來指明多對多關系中的目標類。這里是指Product類。
- <many-to-many>標簽中的column屬性用來指定連接表與目標類的映射表鏈接的外鍵。這里當然是ProductID.
這里我們跟映射one-to-many關系時一樣,我們沒有必要指定目標類的映射表,因為我們已經指定了目標類,NHibernate會通過目標類的映射文件來找到它的映射表。
這里的<bag>標簽中的cadcade屬性設置為none,這是因為我們在刪掉一個訂單信息時,不想連訂單里的產品信息一并刪掉。
Order類中現在只剩一一個簡單類型,Date,我們不會再花費時間,現在你可以關閉Order類的映射文件。
我們將不再檢查 Product.hbm.xml 文件,上面的文章已經涵蓋了所有 Product 中的關系映射,打開文件去檢查每一項的映射對讀者來說是一個很好的練習。
調試映射文件
大多數映射文件的調試都是在運行期間。當一個程序開始配置NHibernate,程序就會嘗試去編譯所有它能找到的映射文件。如果NHibernate出現問題,將會拋NHibernate.MappingException異常。你可以捕捉這些異常,也可以讓它停止執行。另外,這些異常的異常信息可以在 Log4Net 日志文件中找到。最常見的異常信息將會是這樣:
NHibernateSimpleDemo.Model.Order.hbm.xml --->
NHibernate.PropertyNotFoundException: Could not find a getter for
property 'OrderItems' in class 'NHibernateSimpleDemo.Order'
調試符合一般調試模式,解決Bug,重新編譯,重新執行。如果你的程序能夠完成配置NHibernate,你就應該知道你的映射文件是沒有問題的。下面我們來討論如何配置。
如何NHibernate抱怨其中的一個類沒有映射,盡管你一直為這里類創建了映射文件,那你可以檢查映射文件<class>定義,確定class與映射表匹配是正確的。如果這些都很正確,請確認映射文件的編譯類型是嵌入式資源。
整合NHibernate
這里沒有正確的方式把NHibernate整合到你的應用程序中,但作者的本意是遵循一般的三層架構,把NHibernate的配置和處理代碼放在數據層,示例程序中包含一個 Persistence 文件夾,其中包含一個 PersistenceManager 類。
PersistenceManager類包含了持久化業務模型中的每一個實體的一般函數。單單一個類就已經滿足了示例程序,但是對于軟件產品這并不是一個很好的實踐。在現實的軟件編程中,你可以把這些方法分成多個持久化類。
PersistenceManager類配置NHibernate,而且持有一個SessionFactory對象的引用。一個SessionFactory 可以產生多個 Session 對象。Session是NHiberante中一個工作單元。一個Session代表了一次你的程序和NHibernate的一次交互。
你可以把它們當作成交易層次上一級。一個Session通常只有一次交互,但是它可以包含好幾個。你一個打開一個Session,進行一次或是多次事務,然后關閉它,最后釋放它。
Session通過SessionFactory創建。SessionFactory是密集型資源,而且它的初始化成本較高,從另一方面講,Session使用了有限的資源,而且強加一些初始化成本。所以,一般的做法是在程序初始化時,創建一個全局的SessionFactory。并且在需要使用Session時,才進行創建。
示例程序中,PersistenceManager的初始化只是程序初始化的一部分。PersistenceManager配置了一個SessionFactory。程序使用PersistenceManager.SessionFactory來創建Session。
配置NHibernate
配置NHibernate有兩個元素:配置文件和配置代碼。
配置文件可以放在程序根目錄下的一個單獨的文件里,也可以放在App.config文件里。
在文章的開始,我們建議您下載Log4Net,Log4Net擁有它本身的配置文件,我們也把它放在App.config文件中,NHibernate和Log4Net的配置文件需要放置在App.config文件中的<configSections>標簽中。
NHibernate的配置文件很簡單,這個配置主要是讓NHibernate來創建一個SessionFactory。
- Connection provider:提供連接的工廠。NHibernate應該使用IConnectionProvider。示例程序將使用默認提供的Provider。
- Dialect:數據庫方言。示例程序將使用SQL Server 2005方言。
- Connection driver:數據庫連接驅動。示例程序將使用SQL Server 客戶端驅動。
- Connection String:數據庫連接字符串。
示例程序中的數據庫連接字符串只適合作者的開發環境。你應該修改成符合自己的。
配置NHibernate、配置Log4Net
您應該回憶起我們向示例程序添加了Log4Net的引用。所以配置NHibernate的第一步就是配置Log4Net。Log4Net的配置不是必需的。
現在我們在App.config文件中添加配置:
<!-- Specify the logging level for NHibernates -->
<logger name="NHibernate">
<level value="DEBUG" />
</logger>
接下來為你的類添加下列特性:
namespace NHibernateSimpleDemo
{
public class PersistenceManager : IDisposable
{
// ...
}
...
最后一步調用 Configure() 配置Log4Net。
最后在 PersistenceManager 類的構造方法中調用下列方法:
{
log4net.Config.XmlConfigurator.Configure();
}
配置NHibernate、配置代碼
{
// Initialize
Configuration cfg = new Configuration();
cfg.Configure();
// Add class mappings to configuration object
Assembly thisAssembly = typeof(Customer).Assembly;
cfg.AddAssembly(thisAssembly);
// Create session factory from configuration object
m_SessionFactory = cfg.BuildSessionFactory();
}
首先我們創建一個NHibernate的Configuration對象,通過它來實現從映射文件中映射類。AddAssembly()方法需要所有的映射文件嵌入到項目的程序集中,所以所有的映射文件的BuildAction(編譯方式)設置為Embedded Resource(嵌入式資源)。
接下來我們需要它來創建一個SessionFactory。我們不需要指定配置文件是放在App.config中,還是一個單獨的文件中。
BuildSessionFactory()返回一個SessionFactory對象,傳遞給持久化類的成員變量SessionFactory。
以后程序就可以調用SessionFactory,不管什么時候想使用一個Session對象,它是一個全局的變量。
如果我們使用了很多復雜的類:一個實體類對應一個持久化類,這樣的話我們就需要為每一個類傳遞一個SessionFactory對象的引用。因為示例程序中,我們只有一個持久化類(PersistenceManager),所以我們可以把SessionFactory當作成員變量來調用。
使用NHibernate持久化類
NHibernate一個非常強大的特性就是實現聯級地保存和刪除。例如:當我們保存Customer對象時,NHibernate也會自動將Customer對象下的所有的Order中變化的部分也保存進去。這個功能大大簡化了我們持久化的代碼。
PersistenceManager類包含了實現了基本的增刪改查操作的方法。
Save():保存實體。
RetrieveAll():從數據庫中遍歷給定類型的所有對象。
RetrieveEquals():返回對象中的一個屬性等于給定值的所有對象。
Delete():這個方法有兩個重載方法,第一個方法刪除一個實體,第二個方法刪除一個序列的實體。
所有的方法遵循以下幾個常規模式:
使用 using 來包裝一個NHibernate Session對象,這樣可以保證Session在方法執行完畢時自動關閉和自動釋放,即使方法執行過程中出現異常。
Save()和Delete()方法都是用 using 來包裝一個 Transaction。同樣是為了自動釋放和關閉Transaction的資源。
這些方法都調用Session對象的一般方法。
PersistenceManager類包含一個 Close()方法,而且實現IDisposible接口。這就意味著在程序關閉的時候,必須釋放PersistenceManager的所有資源。
PersistenceManager類中的CRUD(創建,遍歷,更新,刪除)方法不代表一個對象持久化的所有實現。CRUD方法只是用來顯示基本的NHibernate的持久化是怎樣工作的。NHibernate文檔里有詳細的解釋。
回報
到了現在你也許會有疑問,NHibernate是一個如此復雜的配置過程,那他應該和手動寫CRUD函數一樣簡單。我個人認為NHibernate的學習過程是非常艱難的,而且進展很慢。
好了,現在就是他回報的時候,你是不是覺得到現在我們只不過創建了幾個映射文件,還有添加那么幾行代碼而已。一旦你了解了這個系統,它真的不是很壞。你獲得的也是相關可觀的。
想想我們以前通過手寫代碼獲取Customer序列,然后獲取每一個Customer的Order對象序列,還有每一個Order的Product對象序列。
看看NHibernate是怎么做的:
下面就是保存的代碼:
{
using (ISession session = m_SessionFactory.OpenSession())
{
using (session.BeginTransaction())
{
session.SaveOrUpdate(item);
session.Transaction.Commit();
}
}
}
運行實例程序
示例程序是一個控制臺的應用程序,所有它沒有用戶入口。但是Program類承擔了這個角色。Main()方法跟Controller類交互。另外Program類注冊了OrderSystem.Populate事件,當OrderSystem被重新重載時觸發。
這種方式是對MVC架構的一種實現。如果想對MVC有一個深入的了解,閱讀這里。如果你對MVC還不夠熟悉,通過閱讀Main()方法對MVC流程有很好的幫助。
保存一個業務模型
當清理完數據庫,程序在內存中創建業務模型。OrderSystem.Populate()方法開始工作,當其執行完畢后,觸發Populated事件,這個事件提示程序業務模型已經從數據庫中重新加載。Program類注冊這個事件,用它來打印所有Customer信息和Order信息。
一旦業務模型被創建,程序就會保存。PersistenceManager中的Save<T>()方法展現了使用NHibernate來持久化代碼是多么簡單。我們甚至不用再考慮數據庫。Controller簡單的敘述了PersistenceManager怎樣來保存對象。
(OrderSystem OrderSystem, PersistenceManager persistenceManager)
{
// Save Products
foreach (Product product in OrderSystem.Catalog)
{
persistenceManager.Save<Product>(product);
}
// Save Customers (also saves Orders)
foreach (Customer Customer in OrderSystem.Customers)
{
persistenceManager.Save<Customer>(Customer);
}
}
SaveBusinessObjects()方法保存了Products和Customers信息,但是沒有保存Orders信息。因為我們已經在Customer.Orders屬性上把cascading打開,所以Customer.Orders信息在我們保存Customer時將會自動保存。
也會關注為OrderItems表創建條目,即使我們沒有為這些寫代碼。這是因為在Order類的映射文件中,我們已經指定OrderItems表作為連接表在于Order.OrderItems關聯的多對多關系中。
刪除業務對象
保存了業務模型之后,程序需要將它們從RAM中清理。從RAM中刪除業務對象并不代碼從數據庫中刪除。如果我們從RAM刪除了業務對象,NHibernate可以從數據庫中隨時加載出來。
使用NHibernate加載對象
一旦業務模型在RAM中被刪除,程序就會從數據庫中重新加載出來。這一步我們將演示怎樣加載持久阿虎對象。程序使用Controller。LoadBusinessObjects()方法來加載對象,這個方法跟Program類中的方法一樣簡單,也使用 using 來包裝。它使用NHibernate中很流行的“Query By Criteria”特性。
{
using (ISession session = m_SessionFactory.OpenSession())
{
// Retrieve all objects of the type passed in
ICriteria targetObjects = m_Session.CreateCriteria(typeof(T));
IList<T> itemList = targetObjects.List<T>();
// Set return value
return itemList;
}
}
對象的類型通過T參數來制定,這個函數創建一個ICriteria對象,調用List<T>()方法,來返回符合標準的一個對象的集合。
示例程序加載了兩次Order對象,一次是明確的,一次是含蓄的。
當加載OrderSystem.Orders集合時,明確加載了一次Orders對象
當加載OrderSystem.Customers集合時含蓄的加載了一次Orders對象。由于Customer映射文件 中的<bag>標簽中的cascade屬性的設置,程序會在加載Customer對象的時候自動加載Orders對 象。
話句話說,cascading會作用在加載時,同樣也會作用在保存的時候。如果沒有Order集合,我們就不用明確的加載它了。但是加載Orders對象會導致一個風險,就是當我們想創建兩個不用引用的Orders對象時,創建兩個不同的Orders對象“他們代表著同一個Orders”。
這里是我們怎么解決這個問題。首先我們設定Session變量為成員變量,而不是本地變量。這樣可以保證每一個Session變量加載的是同一種對象。接下來我們為這個方法添加一個新的參數,SessionAction。這個參數用來指定函數使用哪一個session。
Begin:這個方法返回一個新的Session對象。
Continue:這個方法持續一個已經存在的session對象。
End:這個方法持續一個已經存在的session對象,并且在方法完畢時結束它。
BeginAndEnd:這個方法開始一個新的session(會話)并在方法結束時結束它。
修改后的方法可以加載許多類型不同的對象,因為他們需要在同一個會話中加載,保證了對象的標識加載了不止一次。
{
// Open a new session if specified
if ((sessionAction == SessionAction.Begin) || (sessionAction ==
SessionAction.BeginAndEnd))
{
m_Session = m_SessionFactory.OpenSession();
}
// Retrieve all objects of the type passed in
ICriteria targetObjects = m_Session.CreateCriteria(typeof(T));
IList<T> itemList = targetObjects.List<T>();
// Close the session if specified
if ((sessionAction == SessionAction.End) || (sessionAction ==
SessionAction.BeginAndEnd))
{
m_Session.Close();
m_Session.Dispose();
}
// Set return value
return itemList;
}
程序只會在最上面的持久化類上調用RetrieveAll<T>()方法,這些類是:Product,Customer,Orders。我們沒有必要明確地遍歷OrderItems,也沒有必要明確的為Customer加載Orders,或者為Orders加載OrderItems。NHibernate會照顧到所有子對象。
NHibernate在查詢數據庫上有好幾種特性:
1.Query by criteria:通過創建Criteria對象查詢數據庫。
2.Query by example:通過創建一個你想要遍歷類型的對象,設置它的屬性來指定某個criteria來查詢數據庫。
3.Hibernate Query Language:一種跟SQL差不多的查詢語言。
4.SQL語句:也可以通過原生SQL來查詢數據庫,如果上面幾個方法不能滿足你的需求的話。
SQL語句的方式應該是您選擇的最后一種方式,如果上面幾種方法不適合您的話。NHibernate文檔有詳細的解釋。
驗證對象標識
一旦示例程序重新加載業務模型以后,它標識對象的標識已經被保存。事實上有兩個對象加載了兩次:
Order對象在OrderSystem.Orders中明確加載一次,在OrderSystem.Customers[i].Orders中含蓄的加載一次。
另外,Customer對象在OrderSystem.Customers中明確加載一次,在OrderSystem.Orders[i].Customer中含蓄的加載一次。
第一個Customer放置了第一個Order。我們可以測試這個:
1.Customer集合中的第一個Customer和Order集合中的第一個Order的Customer是同一個對象。
2.Order集合中的第一個Order和Customer集合中的第一個Customer對象的Order是同一個對象。
我們通過使用object.ReferenceEquals()方法來判斷兩個對象是不是同一個引用。
Customer CustomerA = OrderSystem.Customers[0];
Customer CustomerB = OrderSystem.Orders[0].Customer;
bool sameObject = object.ReferenceEquals(CustomerA, CustomerB);
// Compare Order #1 to the Customer #1 order--should be equal
Order orderA = OrderSystem.Orders[0];
Order orderB = OrderSystem.Customers[0].Orders[0];
sameObject = object.ReferenceEquals(CustomerA, CustomerB);
從數據庫中刪除對象
上面我們已經提到從對象模型中刪除一個對象,并不會從數據庫刪除。示例程序將通過從對象模型中刪除一個對象,再從數據庫從新加載來解釋這一點。
從數據庫中刪除對象,我們需要明確的告訴NHibernate。
{
using (ISession session = m_SessionFactory.OpenSession())
{
foreach (T item in itemsToDelete)
{
using (session.BeginTransaction())
{
session.Delete(item);
session.Transaction.Commit();
}
}
}
}
示例程序展示了從數據庫刪除第一個Customer對象。然后將會清理對象模型,在清理之前,會重新從數據庫中加載對象模型。NHibernate在刪除Customer記錄的同時,同時也刪除了Customer的Orders,還有每一個Order下的OrderItems。
把焦點返回到它屬于哪里
現在我們已經完成了NHibernate基本的CRUD操作。NHibernate將會大大簡化你程序中的持久化代碼層,我希望你可以帶著這個觀點離開。一旦你為你的類創建了映射文件,就可以忘記你的持久層,而且你也不用花費幾天時間或是幾周來編寫一個累贅的持久層,才知道NHibernate這么多好處。
這里還有另外一個好處,也許是最重要的一個。在VS 2005的項目瀏覽器中看看這個示例程序,你會發現不部分的工作已經在Model層實現了,Controller管理業務模型和持久管理器中的工作和代理任務。這樣的結果就是你可以自由地把你的注意致力于業務模型上。減輕了你寫持久化代碼的苦差事,NHibernate能夠讓你的焦點放在你的業務模型上,這是它屬于的地方。
英文原版:http://www.codeproject.com/KB/database/Nhibernate_Made_Simple.aspx
總結
有些術語翻譯的不對的地方,請大家多多指正,一起學習NHibernate。
上面介紹的加載映射文件,以及配置PersistenceManager、SessionFactory如果搭配Spring.NET,上面的所有配置代碼,完全可以通過Spring的依賴注入實現,讓代碼看起來更加簡單。
上面還有提到所有要映射的類的屬性需要聲明為Virtual,當然現在也可以不用聲明為virtual。
上面提到的一個對象實際上加載兩次,在現實的項目中,我們一般是不會把lazy設置為true的,因為當一個對象的子對象非常多的時候,NHibernate遍歷其子對象是一個非常耗資源的過程。
Spring.NET搭配NHibernate對于.NET絕對是一個非常合適的選擇。
Xuem www.cnblogs.com/daydayfree