目錄
寫在前面
上篇文章簡單介紹了,Fluent Nhibernate使用代碼的方式生成Nhibernate的配置文件,以及如何生成持久化類的映射文件。通過上篇的學習你會發現,Fluent Nhibernate仍然需要引用Nhibernate的兩個程序集(Nhibernate.dll和Iesi.Collections.dll),所以與Nhibernate最大的區別就在生成配置文件的方式上面,這里關於Nhibernate的特性方面就不再多贅述,可以參考Nhibernate相關的文章。這里主要研究一下Nhibernate中需在配置文件中進行配置的一些特性。那么就先提一下一對多關系的處理。
測試用的數據庫仍然采用學習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之旅二--Entity Mapping
Fluent NHibernate RC 1.0 --升級內容
[原創]Fluent NHibernate之旅(三)-- 繼承
[原創]Fluent NHibernate之旅(四)-- 關系(上)