本節內容
引入
NHibernate中的集合類型
建立父子關系
父子關聯映射
結語
引入
通過前幾篇文章的介紹,基本上了解了NHibernate,但是在NHibernate中映射關系是NHibernate中的亮點,也是最難掌握的技術。從這篇開始學習這些東西,我將圖文結合來說明這裡奧秘的知識。
前幾篇,我們的例子只使用了一個簡單的Customer對象。但是在客戶/訂單/產品的經典組合中,他們的關系非常復雜?讓我們先回顧在第二篇中建立的數據模型。
在圖上,我已經清晰的標注了表之間的關系,首先分析Customer和Order之間的“外鍵關系”或者稱作“父子關系”、“一對多關系”。在分析之前先初步了解NHibernate中的集合。
NHibernate中的集合類型
NHibernate支持/定義的幾種類型的集合:
Bag:對象集合,每個元素可以重復。例如{1,2,2,6,0,0},在.Net中相當於IList或者IList<T>實現。
Set:對象集合,每個元素必須唯一。例如{1,2,5,6},在.Net中相當於ISet或者ISet<T>實現,Iesi.Collections.dll程序集提供ISet集合。
List:整數索引對象集合,每個元素可以重復。例如{{1,"YJingLee"},{2,"CnBlogs"},{3,"LiYongJing"}},在.Net中相當於ArraryList或者List<T>實現。
Map:鍵值對集合。例如{{"YJingLee",5},{"CnBlogs",7},{"LiYongJing",6}},在.Net中相當於HashTable或者IDictionary<Tkey,TValue>實現。
實際上,我們大多數情況下使用Set集合類型,因為這個類型和關系型數據庫模型比較接近。
建立父子關系
直接看下面一幅圖的兩張表:
上面兩張表關系表達的意思是:Customer有一個或多個Orders,Orders屬於一個Customer。一般而言,我們稱Customer為“父”,Order稱為“子”。Customer和Order之間關系就有幾種說法:“外鍵關系”、“父子關系”、“一對多關系”都可以。
1.Customer有一個或多個Orders
在對象模型中:在Customer類中把Orders作為一個集合,這時可以說Customer對象包含了Orders集合。在.NET中通常這樣表述:
public class Customer
{
//......
public IList<Order> Orders{ get; set; }
}
訪問對象方式:通過子集合訪問:Customer.Orders[...]
在NHibernate中,通常而言使用Iesi.Collections.dll程序集中的ISet集合,現在修改Customer.cs類,首先需要引用這個程序集,Customer.cs類代碼如下:
using Iesi.Collections.Generic;
namespace DomainModel.Entities
{
public class Customer
{
public virtual int CustomerId { get; set; }
public virtual string Firstname { get; set; }
public virtual string Lastname { get; set; }
//一對多關系:Customer有一個或多個Orders
public virtual ISet<Order> Orders { get; set; }
}
}
2.Order屬於一個Customer
在對象模型中:在Order類中把Customer作為單一對象,這時可以說Order對象包含了一個Customer。在.NET中通常這樣表述:
public class Order
{
//......
public Customer Customer{ get; set; }
}
其訪問對象方式:通過父對象成員訪問:Order.Customer
我們在項目DomainModel層的Entities文件夾中新建Order.cs類,編寫代碼如下:
namespace DomainModel.Entities
{
public class Order
{
public virtual int OrderId { get; set; }
public virtual DateTime OrderDate { get; set; }
//多對一關系:Orders屬於一個Customer
public virtual Customer Customer { get; set; }
}
}
好了,我們現在完成持久類了,下面看看這兩個類如何映射。
父子關聯映射
在NHibernate中,我們可以通過映射文件來關聯對象之間的關系。映射文件定義了:
對象之間關系:一對一、一對多、多對一、多對多關系。
在關系中控制級聯行為(Cascade behavior):級聯更新、級聯刪除
父子之間的雙向導航(bidirectional navigation)
1.父實體映射
父實體(Customer)映射定義了:
集合類型(Bag、Set、List、Map)
在保存、更新、刪除操作時的級聯行為
關聯的控制方向:
Inverse="false"(默認):父實體負責維護關聯關系
Inverse="true":子實體負責維護關聯關系
與子實體關聯的關系(一對多、多對一、多對多)
這些具體的設置是NHibernate中的難點所在,以後慢慢討論這些不同設置下的奧秘之處。
這一篇初步建立Customer與Order的一對多關系,修改Customer.hbm.xml映射文件如下:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="DomainModel" namespace="DomainModel">
<class name ="DomainModel.Entities.Customer,DomainModel"
table="Customer">
<id name="CustomerId" column="CustomerId" type="Int32"
unsaved-value="0">
<generator class ="native"></generator>
</id>
<property name="Firstname" column ="Firstname" type="string"
length="50" not-null="false"/>
<property name ="Lastname" column="Lastname" type="string"
length="50" not-null="false"/>
<!--一對多關系:Customer有一個或多個Orders-->
<set name="Orders" table="`Order`" generic="true" inverse="true">
<key column="Customer" foreign-key="FK_CustomerOrders"/>
<one-to-many class="DomainModel.Entities.Order,DomainModel"/>
</set>
</class>
</hibernate-mapping>
可以看到,在“父”端映射使用Set元素,標明屬性名稱、表名、子實體負責維護關聯關系。
2.子實體映射
子實體(Order)映射定義的東西就是父實體少了:與父實體關聯的(多對一、一對多、多對多) 關系,並用一個指針來導航到父實體。
在“子”端通過many-to-one元素定義與“父”端的關聯,從“子”端角度看這種關系模型是多對一關聯(實際上是對Customer對象的引用)。下面看看many-to-one元素映射屬性:
看看這些映射屬性具體有什麼意義:
access(默認property):可選field、property、nosetter、ClassName值。NHibernate訪問屬性的策略。
cascade(可選):指明哪些操作會從父對象級聯到關聯的對象。可選all、save-update、delete、none值。除none之外其它將使指定的操作延伸到關聯的(子)對象。
class(默認通過反射得到屬性類型):關聯類的名字。
column(默認屬性名):列名。
fetch(默認select):可選select和join值,select:用單獨的查詢抓取關聯;join:總是用外連接抓取關聯。
foreign-key:外鍵名稱,使用SchemaExport工具生成的名稱。
index:......
update,insert(默認true):指定對應的字段是否包含在用於UPDATE或INSERT 的SQL語句中。如果二者都是false,則這是一個純粹的 “外源性(derived)”關聯,它的值是通過映射到同一個(或多個)字段的某些其他特性得到或者通過觸發器其他程序得到。
lazy:可選false和proxy值。是否延遲,不延遲還是使用代理延遲。
name:屬性名稱propertyName。
not-found:可選ignore和exception值。找不到忽略或者拋出異常。
not-null:可選true和false值。
outer-join:可選auto、true、false值。
property-ref(可選):指定關聯類的一個屬性名稱,這個屬性會和外鍵相對應。如果沒有指定,會使用對方關聯類的主鍵。這個屬性通常在遺留的數據庫系統使用,可能有外鍵指向對方關聯表的某個非主鍵字段(但是應該是一個唯一關鍵字)的情況下,是非常不好的關系模型。比如說,假設Customer類有唯一的CustomerId,它並不是主鍵。這一點在NHibernate源碼中有了充分的體驗。
unique:可選true和false值。控制NHibernate通過SchemaExport工具生成DDL的過程。
unique-key(可選):使用DDL為外鍵字段生成一個唯一約束。
我們來建立“子”端到“父”端的映射,新建Order.hbm.xml文件,編寫代碼如下:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="DomainModel" namespace="DomainModel">
<class name="DomainModel.Entities.Order,DomainModel" table="`Order`" >
<id name="OrderId" column="OrderId" type="Int32" unsaved-value="0">
<generator class="native" />
</id>
<property name="OrderDate" column="OrderDate" type="DateTime"
not-null="true" />
<!--多對一關系:Orders屬於一個Customer-->
<many-to-one name="Customer" column="Customer" not-null="true"
class="DomainModel.Entities.Customer,DomainModel"
foreign-key="FK_CustomerOrders" />
</class>
</hibernate-mapping>
關於如何關聯看看上面的屬性就一目了然了。
結語
這一篇建立Customer和Order之間一對多關聯關系,並初步了解NHibernate中的集合。下一篇繼續以這一篇為基礎,一步一步去挖掘,這是我一貫寫法,介紹在NHibernate中使用原生SQL、HQL、Criteria API三種查詢方式來關聯查詢。當然了,從這篇開始,研究的東西就更多了,像查詢、加載機制、代理機制、再議並發控制、NHibernate提供的有用類等等。