[Fluent NHibernate]一對多關系處理


目錄

寫在前面

系列文章

一對多關系

總結

寫在前面

上篇文章簡單介紹了,Fluent Nhibernate使用代碼的方式生成Nhibernate的配置文件,以及如何生成持久化類的映射文件。通過上篇的學習你會發現,Fluent Nhibernate仍然需要引用Nhibernate的兩個程序集(Nhibernate.dll和Iesi.Collections.dll),所以與Nhibernate最大的區別就在生成配置文件的方式上面,這里關於Nhibernate的特性方面就不再多贅述,可以參考Nhibernate相關的文章。這里主要研究一下Nhibernate中需在配置文件中進行配置的一些特性。那么就先提一下一對多關系的處理。

測試用的數據庫仍然采用學習Nhibernate時,使用的數據庫。

系列文章

耗時兩月,NHibernate系列出爐

[Fluent NHibernate]第一個程序

一對多關系

這里將數據庫表及關系圖貼出,方面查看:

這里就使用一個客戶可以對應多個訂單的一對多關系進行分析。

首先在持久化類中添加一對多關系的處理

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 using NHibernate;
 7 using FluentNHibernate;
 8 namespace Wolfy.Domain.Entities
 9 {
10       /// <summary>
11     /// 描述:客戶實體,數據庫持久化類
12     /// 創建人:wolfy
13     /// 創建時間:2014-10-16
14     /// </summary>
15     public class Customer
16     {
17         /// <summary>
18         /// 客戶id
19         /// </summary>
20         public virtual Guid CustomerID { get; set; }
21         /// <summary>
22         /// 客戶名字
23         /// </summary>
24         public virtual string CustomerName { get; set; }
25         /// <summary>
26         /// 地址
27         /// </summary>
28         public virtual string CustomerAddress { set; get; }
29         /// <summary>
30         /// 版本控制
31         /// </summary>
32         public virtual int Version { get; set; }
33         /// <summary>
34         /// 一對多關系:一個Customer有一個或者多個Order
35         /// </summary>
36         public virtual System.Collections.Generic.ISet<Order> Orders { set; get; }
37     }
38 }
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 
 7 namespace Wolfy.Domain.Entities
 8 {   /// <summary>
 9     /// 描述:訂單實體,數據庫持久化類
10     /// 創建人:wolfy
11     /// 創建時間:2014-10-16
12     /// </summary>
13     public class Order
14     {
15         /// <summary>
16         /// 訂單id
17         /// </summary>
18         public virtual Guid OrderID { set; get; }
19         /// <summary>
20         /// 下訂單時間
21         /// </summary>
22         public virtual DateTime OrderDate { set; get; }
23         /// <summary>
24         /// 下訂單的客戶,多對一的關系:orders對應一個客戶
25         /// </summary>
26         public virtual Customer Customer { set; get; }
27     }
28 }

在Nhibernate中處理一對多關系的映射文件為:

Customer.hbm.xml

 1 <?xml version="1.0" encoding="utf-8" ?>
 2 <!--assembly:程序集,namespace:命名空間-->
 3 <hibernate-mapping  xmlns="urn:nhibernate-mapping-2.2" assembly="Wolfy.Shop.Domain"  namespace="Wolfy.Shop.Domain.Entities">
 4   <!--存儲過程-->
 5   <class name="Wolfy.Shop.Domain.Entities.Customer,Wolfy.Shop.Domain" table="TB_Customer">
 6     <!--二級緩存-->
 7     <cache usage="read-write"/>
 8     <!--主鍵-->
 9     <id name="CustomerID" type="Guid" unsaved-value="null">
10       <column name="CustomerID" sql-type="uniqueidentifier" not-null="true" unique="true" />
11       <generator class="assigned"></generator>
12     </id>
13     <!--版本控制-->
14     <version name="Version" column="Version" type="integer"  unsaved-value="0"/>
15     <!--一對多關系:一個客戶可以有一個或者多個訂單-->
16     <!--子實體負責維護關聯關系-->
17     <set name="Orders" table="TB_Order" generic="true" inverse="true" cascade="all">
18       <key column="CustomerID" foreign-key="FK_TB_Order_TB_Customer"></key>
19       <one-to-many class="Wolfy.Shop.Domain.Entities.Order,Wolfy.Shop.Domain"/>
20     </set>
21 </class>
22     </hibernate-mapping>

Order.hbm.xml

 1 <?xml version="1.0" encoding="utf-8" ?>
 2 <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Wolfy.Shop.Domain"  namespace="Wolfy.Shop.Domain.Entities">
 3   <class name="Wolfy.Shop.Domain.Entities.Order,Wolfy.Shop.Domain" table="TB_Order">
 4     <id name="OrderID" column="OrderID" type="Guid" unsaved-value="null">
 5       <generator class="assigned" />
 6     </id>
 7     <property name="OrderDate" column="OrderDate" type="DateTime"
 8               not-null="true" />
 9     <!--多對一關系:Orders屬於一個Customer-->
10     <many-to-one  name="Customer" column="CustomerID" not-null="true" 
11                  class="Wolfy.Shop.Domain.Entities.Customer,Wolfy.Shop.Domain"
12                  foreign-key="FK_TB_Order_TB_Customer" />
13      </class>
14 </hibernate-mapping>

那么Fluent Nhibernate對於一對多關系是如何處理的呢?

添加Order的映射類

 1 using FluentNHibernate.Mapping;
 2 using FluentNHibernate;
 3 using System;
 4 using System.Collections.Generic;
 5 using System.Linq;
 6 using System.Text;
 7 using System.Threading.Tasks;
 8 using Wolfy.Domain.Entities;
 9 namespace Wolfy.Domain.Mapping
10 {
11     /// <summary>
12     /// 描述:訂單實體映射類
13     /// 創建人:wolfy
14     /// 創建時間:2014-12-07
15     /// </summary>
16     public class OrderMapping : ClassMap<Order>
17     {
18         public OrderMapping()
19         {
20             //指定對應的數據表
21             Table("TB_Order");
22             //指定id主鍵
23             Id<Guid>("OrderID").GeneratedBy.Guid();
24             //映射其他的字段
25             Map(m => m.OrderDate).Nullable();
26             //處理多對一關系,多個order可以屬於一個customer
27             References<Customer>(r => r.Customer).Column("CustomerID").ForeignKey("CustomerID").Cascade.All();
28         }
29     }
30 }

在Order映射類中處理多對一關系使用References,更符合面向對象的概念,在實體類中的關系就這種引用的關系。

修改Customer映射類

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 using NHibernate;
 7 using FluentNHibernate.Mapping;
 8 using Wolfy.Domain.Entities;
 9 namespace Wolfy.Domain.Mapping
10 {
11     /// <summary>
12     /// Customer映射實體類,需要集成ClassMap泛型類
13     /// </summary>
14     public class CustomerMapping : ClassMap<Customer>
15     {
16         /// <summary>
17         /// 映射關系實體類的構造函數
18         /// 在構造函數中處理好映射關系
19         /// </summary>
20         public CustomerMapping()
21         {
22             //指定持久化類對應的數據表
23             Table("TB_Customer");
24             //自動增長的id
25             //Id(i => i.CustomerID);
26             //映射關系
27             Id<Guid>("CustomerID").GeneratedBy.Guid();
28             Map(m => m.CustomerAddress).Length(50).Nullable();
29             Map(m => m.CustomerName).Length(32).Nullable();
30             Map(m => m.Version);
31             //處理一對多關系的映射,一個客戶可以有多個訂單
32             //關聯的數據表進行懶加載,主鍵名為CustomerID,級聯關系所有操作,cascade:All|delete|saveorUpdate
33             HasMany<Order>(h => h.Orders).LazyLoad().AsSet().KeyColumn("CustomerID").Cascade.All();
34         }
35     }
36 }

單元測試

描述:創建一個客戶對象,並向客戶的訂單集合中添加兩個訂單,並斷言結果為true,即添加成功。

 1        [TestMethod]
 2         public void AddCustomerTest2()
 3         {
 4             var customer = new Customer()
 5             {
 6                 Version = 1,
 7                 CustomerName = "wolfy",
 8                 CustomerAddress = "中國 北京",
 9                 CustomerID = Guid.NewGuid()
10             };
11             customer.Orders = new HashedSet<Order>();
12             customer.Orders.Add(new Order() { Customer = customer, OrderDate = DateTime.Now, OrderID = Guid.NewGuid() });
13             customer.Orders.Add(new Order() { Customer = customer, OrderDate = DateTime.Now, OrderID = Guid.NewGuid() });
14             var result = _customerData.AddCustomer(customer);
15             Assert.IsTrue(result);
16         }

運行測試

生成的sql語句

跟使用配置文件的對比可參考我這篇文章:http://www.cnblogs.com/wolf-sun/p/4068749.html

那么看一下在c盤生成的xml文件:

xml文件內容為:

 1 <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
 2   <class xmlns="urn:nhibernate-mapping-2.2" name="Wolfy.Domain.Entities.Customer, Wolfy.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="TB_Customer">
 3     <id type="System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
 4       <column name="CustomerID" />
 5       <generator class="guid" />
 6     </id>
 7     <set cascade="all" lazy="true" name="Orders">
 8       <key>
 9         <column name="CustomerID" />
10       </key>
11       <one-to-many class="Wolfy.Domain.Entities.Order, Wolfy.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
12     </set>
13     <property name="CustomerAddress" type="System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
14       <column name="CustomerAddress" length="50" not-null="false" />
15     </property>
16     <property name="CustomerName" type="System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
17       <column name="CustomerName" length="32" not-null="false" />
18     </property>
19     <property name="Version" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
20       <column name="Version" />
21     </property>
22   </class>
23 </hibernate-mapping>
 1 <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
 2   <class xmlns="urn:nhibernate-mapping-2.2" name="Wolfy.Domain.Entities.Order, Wolfy.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" table="TB_Order">
 3     <id type="System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
 4       <column name="OrderID" />
 5       <generator class="guid" />
 6     </id>
 7     <property name="OrderDate" type="System.DateTime, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
 8       <column name="OrderDate" not-null="false" />
 9     </property>
10     <many-to-one cascade="all" class="Wolfy.Domain.Entities.Customer, Wolfy.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" foreign-key="CustomerID" name="Customer">
11       <column name="CustomerID" />
12     </many-to-one>
13   </class>
14 </hibernate-mapping>

通過與nhibernate手寫的xml映射文件,在結構上,內容上大概相似。

級聯查詢

查詢客戶信息時,將查詢客戶下的所有的訂單

在CustomerData層添加如下方法並進行測試

 1         /// <summary>
 2         /// 獲得客戶信息
 3         /// </summary>
 4         /// <param name="customerID"></param>
 5         /// <returns></returns>
 6         public Customer GetCustomer(Guid customerID)
 7         {
 8             ISession session = FluentNHibernateHelper.GetSession();
 9             return session.Load<Customer>(customerID);
10         }

測試,並運行

1        [TestMethod]
2         public void GetCustomerTest()
3         {
4             var customer = _customerData.GetCustomer(new Guid("EB99D91A-F504-4BB4-8E52-EA10C96E6637"));
5             Assert.IsNotNull(customer);
6             Console.WriteLine("該客戶的訂單數量:" + customer.Orders.Count);
7         }

測試結果

這先查詢TB_Customer表,然后需要用到Order的數量,所以又查詢了TB_Order表。回頭看看我們在Customer的映射類中指定了使用懶加載的方式

1  HasMany<Order>(h => h.Orders).LazyLoad().AsSet().KeyColumn("CustomerID").Cascade.All();

測試是否是懶加載?

1         [TestMethod]
2         public void GetCustomerTest()
3         {
4             var customer = _customerData.GetCustomer(new Guid("EB99D91A-F504-4BB4-8E52-EA10C96E6637"));
5             Assert.IsNotNull(customer);
6             Console.WriteLine("客戶姓名:"+customer.CustomerName);
7             // Console.WriteLine("該客戶的訂單數量:" + customer.Orders.Count);
8         }

測試結果

在這個測試里面,我們並沒有用到Order,所以就查詢customer的信息,需要的時候再去查詢Order表。在使用時發現,如果不輸出CustomerName,連查詢TB_Customer表的sql也不生成,充分說明,Nhibernate中的懶加載真夠懶的。

在測試的時候,順手把customer的屬性都輸出了,發現一個自認為不科學的地方:

發現了吧,很奇怪,對比一下生成的映射文件,和手寫的Nhibernate的配置文件,將Customer和Order的映射類的id生成規則修改為:

1  Id<Guid>("CustomerID").GeneratedBy.Assigned();

Fluent Nhibernate的customer映射文件

Nhibernate手寫的配置文件

1 <id name="CustomerID" type="Guid" unsaved-value="null">
2       <column name="CustomerID" sql-type="uniqueidentifier" not-null="true" unique="true" />
3       <generator class="assigned"></generator>
4     </id>

你會發現是何其的相似啊,為什么查詢到Customer對象能拿到CustomerName卻拿不到id呢?
看看Assigned的注釋:

1 // 摘要: 
2         //     lets the application to assign an identifier to the object before Save()
3         //     is called.
4         public TParent Assigned();

大概意思就是:讓應用程序在保存方法被調用前為對象指定一個標識符。

關於guid類型作為主鍵的策略,為什么會返回Guid(0),可以參考這篇文章

http://nhforge.org/blogs/nhibernate/archive/2009/05/21/using-the-guid-comb-identifier-strategy.aspx

如果在映射類中設置主鍵的生成策略為:

1  Id<Guid>("CustomerID").GeneratedBy.GuidComb();

在添加數據的時候,可以不用指定CustomerID=Guid.NewGuid();就好像是自增的int類型的主鍵一樣。
那怎么才能拿到這個id呢?手動寫的配置文件確實是可以拿到id的,而Fluent Nhibernate不可以。

找了很久也沒找到解決的辦法,突然那么靈機一動,既然你返回的是一個0串,也就是沒有賦值的情況,那么何不指定你的映射關系呢?

如下:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 using NHibernate;
 7 using FluentNHibernate.Mapping;
 8 using Wolfy.Domain.Entities;
 9 namespace Wolfy.Domain.Mapping
10 {
11     /// <summary>
12     /// Customer映射實體類,需要集成ClassMap泛型類
13     /// </summary>
14     public class CustomerMapping : ClassMap<Customer>
15     {
16         /// <summary>
17         /// 映射關系實體類的構造函數
18         /// 在構造函數中處理好映射關系
19         /// </summary>
20         public CustomerMapping()
21         {
22             //指定持久化類對應的數據表
23             Table("TB_Customer");
24             //自動增長的id
25             //Id(i => i.CustomerID);
26             //映射關系
27             Id<Guid>("CustomerID").GeneratedBy.GuidComb();
28             //指定主鍵后一定要加上主鍵字段的映射關系,不然返回的id為new Guid(),也就是一串0
29             Map(m => m.CustomerID).Nullable();
30             Map(m => m.CustomerAddress).Length(50).Nullable();
31             Map(m => m.CustomerName).Length(32).Nullable();
32             Map(m => m.Version);
33             //處理一對多關系的映射,一個客戶可以有多個訂單
34             //關聯的數據表進行懶加載,主鍵名為CustomerID,級聯關系所有操作,cascade:All|delete|saveorUpdate,級聯刪除時需加上Inverse()
35             //     Inverse the ownership of this entity. Make the other side of the relationship
36             //     responsible for saving.
37             HasMany<Order>(h => h.Orders).LazyLoad().AsSet().KeyColumn("CustomerID").Cascade.All().Inverse();
38         }
39     }
40 }

從上面的代碼,可以看出指定id后,又指定了CustomerID的映射關系。這樣就可以拿到id了。對於從nhibernate過來的,估計已經思維定勢了,覺得指定id就已經指定了id的映射關系了,確實沒必要再去映射了。
測試結果

問題到此解決。

級聯刪除

級聯刪除的時候,需指定Cascade和Inverse。具體測試可參考Nhibernate中一對多關系級聯刪除內容。

 1         /// <summary>
 2         /// 刪除指定客戶
 3         /// </summary>
 4         /// <param name="customer"></param>
 5         /// <returns></returns>
 6         public bool DeleteCustomer(Customer customer)
 7         {
 8             ISession session = FluentNHibernateHelper.GetSession();
 9             using (var trans = session.BeginTransaction())
10             {
11                 try
12                 {
13                     session.Delete(customer);
14                     session.Flush();
15                     trans.Commit();
16                     return true;
17                 }
18                 catch (Exception)
19                 {
20                     trans.Rollback();
21                     return false;
22                 }
23             }
24         }

單元測試

 1        [TestMethod]
 2        public void DeleteCustomerTest()
 3         {
 4             //得到刪除的對象
 5             Console.WriteLine("查詢要刪除的customer對象");
 6             var customer = _customerData.GetCustomer(new Guid("0D9862A6-D6FE-4861-BA61-A3FA00DC328C"));
 7             Assert.IsNotNull(customer);
 8             Console.WriteLine("將查詢到的對象刪除");
 9             var result = _customerData.DeleteCustomer(customer);
10             Assert.IsTrue(result);
11         }

測試結果

總結

本文涉及到級聯刪除,添加,查詢以及一對多關系的處理等內容。還是那句話,與Nhibernate的區別就是在生成配置文件及持久化類的映射文件的方式上。關於Fluent Nhibernate的其他的操作也是在映射文件上與Nhibernate設置上的區別(個人認為)。這里就不再進行介紹了。收集了一些這方面的文章,供大家學習,確實在目前的項目中沒有用到,這東西,現在學了,也記不住,不經實踐,很難掌握。

[原創]Fluent NHibernate之旅(三)-- 繼承

[原創]Fluent NHibernate之旅

[原創]Fluent NHibernate之旅二--Entity Mapping

Fluent NHibernate RC 1.0 --升級內容

[原創]Fluent NHibernate之旅(三)-- 繼承

[原創]Fluent NHibernate之旅(四)-- 關系(上)

[原創]Fluent NHibernate之旅(四)-- 關系(中)

[原創]Fluent NHibernate之旅(四)-- 關系(下)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM