目錄
寫在前面
上篇文章介紹了nhibernate中對一對多關系進行關聯查詢的幾種方式,以及在使用過程需要注意的問題。這篇文章對多對多關系的查詢處理也采用上篇文章的描述方式進行說明。
文檔與系列文章
[NHibernate]持久化類(Persistent Classes)
[NHibernate]集合類(Collections)映射
[NHibernate]緩存(NHibernate.Caches)
[NHibernate]NHibernate.Tool.hbm2net
[NHibernate]Nhibernate如何映射sqlserver中image字段
[NHibernate]條件查詢Criteria Query
多對多關系關聯查詢
多對多關系非常常見,比如系統中常用的權限管理問題,一個用戶有多個權限,當然一個權限可以屬於多個用戶(這樣描述只是為了對多對多關系有個感性的認識)。那么在咱們的客戶/訂單/產品,這三張表中有沒有多對多的關系呢?
訂單可以有多個產品,一種產品可以屬於多個訂單(這個地方有點繞,如果再有一個數量的字段,比較好理解,下一個訂單,只是庫存減少了,但id還是那個id)。關系圖如下:

其中表TB_OrderProduct為order和product的關系表,字段OrderID和ProductID為聯合主鍵。
添加聯合主鍵的sql語句如下:
1 ALTER TABLE tb_orderproduct WITH NOCHECK ADD 2 CONSTRAINT [PK_orderproduct] PRIMARY KEY NONCLUSTERED 3 ( 4 orderid, 5 productid 6 )
首先修改Order類
1 /// <summary> 2 /// 描述:訂單實體,數據庫持久化類 3 /// 創建人:wolfy 4 /// 創建時間:2014-10-16 5 /// </summary> 6 public class Order 7 { 8 /// <summary> 9 /// 訂單id 10 /// </summary> 11 public virtual Guid OrderID { set; get; } 12 /// <summary> 13 /// 下訂單時間 14 /// </summary> 15 public virtual DateTime OrderDate { set; get; } 16 /// <summary> 17 /// 下訂單的客戶,多對一的關系:orders對應一個客戶 18 /// </summary> 19 public virtual Customer Customer { set; get; } 20 /// <summary> 21 /// 多對多關系,一個訂單下可以有多個產品 22 /// </summary> 23 public virtual IList<Product> Products { set; get; } 24 }
修改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 <!--多對多關系:order下有多個product--> 14 <bag name="Products" generic="true" table="TB_OrderProduct"> 15 <key column="OrderID" foreign-key="FK_TB_OrderProduct_TB_Order"/> 16 <many-to-many column="ProductID" class="Wolfy.Shop.Domain.Entities.Product,Wolfy.Shop.Domain" 17 foreign-key="FK_TB_OrderProduct_TB_Product"/> 18 19 </bag> 20 </class> 21 </hibernate-mapping>
修改Product類
1 /// <summary> 2 /// 描述:商品實體,數據庫持久化類 3 /// 創建人:wolfy 4 /// 創建時間:2014-10-16 5 /// </summary> 6 public class Product 7 { 8 /// <summary> 9 /// 商品id 10 /// </summary> 11 public virtual Guid ProductID { set; get; } 12 /// <summary> 13 /// 商品名稱 14 /// </summary> 15 public virtual string Name { set; get; } 16 /// <summary> 17 /// 商品單價 18 /// </summary> 19 public virtual decimal Price { set; get; } 20 /// <summary> 21 /// 多對多關系:Product屬於多個Orders 22 /// </summary> 23 public virtual IList<Order> Orders { get; set; } 24 25 }
Mappings文件夾中新建Product.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.Product,Wolfy.Shop.Domain" table="TB_Product"> 4 <id name="ProductID" column="ProductID" type="Guid" unsaved-value="null"> 5 <generator class="assigned" /> 6 </id> 7 <property name="Name" column="Name" type="String" 8 not-null="true" /> 9 <property name="Price" column="Price" type="float" 10 not-null="true" /> 11 <!--多對多關系:product屬於多個orders--> 12 <bag name="Orders" generic="true" table="TB_OrderProduct"> 13 <key column="ProductID" foreign-key="FK_TB_OrderProduct_TB_Product"/> 14 <many-to-many column="OrderID" class="Wolfy.Shop.Domain.Entities.Order,Wolfy.Shop.Domain" 15 foreign-key="FK_TB_OrderProduct_TB_Order"/> 16 </bag> 17 </class> 18 </hibernate-mapping>
修改映射文件的屬性,這個動作應該養成一個習慣,不管你寫的配置文件對不對,只要添加了映射文件,就修改它的屬性,如圖:
通過上面對比,你會發現多對多關系many-to-many節點的配置相似,只是方向有點區別。
原生SQL查詢
查詢客戶的所有的訂單和訂單下所有的產品。
CustomerData.cs
1 /// <summary> 2 /// 通過sql查詢客戶下的訂單和產品信息 3 /// </summary> 4 /// <param name="cutomerID"></param> 5 /// <returns></returns> 6 public IList<Customer> GetCustomerOrderProductsBySQL(Guid cutomerID) 7 { 8 //獲得ISession實例 9 ISession session = NHibernateHelper.GetSession(); 10 //使用inner join關聯多個表進行查詢 11 return session.CreateSQLQuery("select distinct c.* from TB_Customer c" + 12 " inner join TB_Order o on o.CustomerID=c.CustomerID" + 13 " inner join TB_OrderProduct op on o.OrderID=op.OrderID" + 14 " inner join TB_Product p on op.ProductID=p.ProductID where c.CustomerID=:CustomerID") 15 //使用AddEntity設置返回的實體。 16 .AddEntity("Customer", typeof(Customer)) 17 .SetGuid("CustomerID", cutomerID) 18 .List<Customer>(); 19 }
生成的sql語句
HQL查詢
查詢客戶的所有的訂單和訂單下所有的產品。
CustomerData.cs
1 /// <summary> 2 /// 通過HQL查詢客戶下的訂單和產品信息 3 /// </summary> 4 /// <param name="cutomerID"></param> 5 /// <returns></returns> 6 public IList<Customer> GetCustomerOrderProductsByHQL(Guid cutomerID) 7 { 8 //獲得ISession實例 9 ISession session = NHibernateHelper.GetSession(); 10 //使用HQL基於面向對象的查詢方式 11 return session.CreateQuery("select distinct c from Customer c inner join c.Orders o inner join o.Products where c.CustomerID=:CustomerID") 12 .SetGuid("CustomerID", cutomerID) 13 .List<Customer>(); 14 }
生成的sql語句

通過查看sql及HQL語句可以知道,c.Orders o inner join o.Products 內部為我們查詢了所有Order下的所有產品,你會發現我們的查詢中並沒有出現表TB_OrderProduct,而在sql語句中出現了該表,因為這些信息在映射文件已經描述了。nhibernate通過映射文件,知道如何關聯,該關聯那張數據表。
public IList<Customer> UseHQL_GetCustomersWithOrdersHavingProduct(DateTime orderDate) { return _session.CreateQuery("select distinct c from Customer c ," + " c.Orders.elements o where o.OrderDate > :orderDate") .SetDateTime("orderDate", orderDate) .List<Customer>(); }
上面這段代碼是@李永京文章中的,這種方式也嘗試了c.Orders.elements,總是報以下異常:
NHibernate.Hql.Ast.ANTLR.QuerySyntaxException : c.Orders.elements is not mapped [select distinct c from Customer c , c.Orders.elements o where o.OrderDate > :orderDate]
也許,大概elements在nhibernate 4.0版本中廢除了吧,最后沒辦法,就通過我上面寫的那種方式實現了HQL查詢,畢竟條條大路通羅馬,不能在一條路上吊死吧。
Criteria API關聯查詢
通過HQL方式,我們已經知道了實體間的關系已經在映射文件中定義好了,所以我們在查詢子對象使用子CreateCriteria語句關聯對象之間導航,可以很容易地在實體之間指定約束。這里第二個CreateCriteria()返回ICriteria的新實例,並指向Orders實體的元素。第三個指向Products實體的元素。
查詢客戶的所有的訂單和訂單下所有的產品。
CustomerData.cs
1 /// <summary> 2 /// 通過HQL查詢客戶下的訂單和產品信息 3 /// </summary> 4 /// <param name="cutomerID"></param> 5 /// <returns></returns> 6 public IList<Customer> GetCustomerOrderProductsByCriteriaAPI(Guid cutomerID) 7 { 8 //獲得ISession實例 9 ISession session = NHibernateHelper.GetSession(); 10 return session.CreateCriteria(typeof(Customer)) 11 .Add(Restrictions.Eq("CustomerID", cutomerID)) 12 .CreateCriteria("Orders") 13 .CreateCriteria("Products") 14 .List<Customer>(); 15 }
生成的sql語句

此時查詢出來的數據有兩條,因為有兩個產品,沒有對其進行去重。
測試數據如下:
TB_Customer表

TB_Order表

TB_OrderProduct表

TB_Product表

總結
在學習多對多關聯查詢時,在c.Orders.elements的地方卡在那個地方了,這塊還需要在查一查nhibernate版本之間是不是有差異。被一個問題,折騰到現在,本來打算十點半睡覺的(每天最晚十點半上床睡覺),我基本是不熬夜的,有點強迫症,不想帶着問題睡覺,很晚了,就寫到這兒吧。
參考地址:http://www.cnblogs.com/lyj/archive/2008/10/27/1320764.html#!comments
