目录
写在前面
在前面的文章中,我们只使用了一个Customer类进行举例,而在客户、订单、产品中它们的关系,咱们并没有涉及,比如一个客户可以有一个或者多个订单,在数据库中变现为“主外键关系”,有时也喜欢称为“父子关系”。那么就让我们一起学习,在nhibernate中,是如何处理这种关系的吧?
文档与系列文章
[NHibernate]持久化类(Persistent Classes)
[NHibernate]集合类(Collections)映射
[NHibernate]缓存(NHibernate.Caches)
[NHibernate]NHibernate.Tool.hbm2net
[NHibernate]Nhibernate如何映射sqlserver中image字段
[NHibernate]条件查询Criteria Query
一对多关系
首先看一下数据表的关系。
这里先使用Customer和Order的关系为例进行说明,一个客户可以有一个或者多个订单的关系。
在nhibernate中定义了多种集合方式:
Bag:对象集合,集合中的元素可以重复。例如:{1,2,3,4,2,3},相当于.Net中的IList和IList<T>。
Set:对象集合,集合中的元素必须唯一。例如:{1,3,4},相当于.Net中的ISet和ISet<T>,nhibernate的Iesi.Collections.dll程序集提供ISet集合。
List:整数索引集合,集合中的元素可以重复。例如:{{1,"zhangsan"},{2,"lisi"}},相当于.Net中ArrayList和List集合。
Map:键值对集合,相当于.Net中的IDictionary<TKey,TValue>和Hashtable。
使用过Entity Framework的朋友都知道,在EF中使用的ISet,为了保持一直,这里也使用ISet集合,也方便以后如果使用ef的时候,用起来更顺手。
一个例子
在nhibernate-4.0版本中,在命名空间Iesi.Collections.Generic中已经没有ISet集合了,那么我们使用.net中的ISet代替来描述这种一对多的关系,Customer.cs类代码如下:
1 /// <summary> 2 /// 描述:客户实体,数据库持久化类 3 /// 创建人:wolfy 4 /// 创建时间:2014-10-16 5 /// </summary> 6 public class Customer 7 { 8 /// <summary> 9 /// 客户id 10 /// </summary> 11 public virtual Guid CustomerID { get; set; } 12 /// <summary> 13 /// 客户名字 14 /// </summary> 15 public virtual Name NameAddress { get; set; } 16 /// <summary> 17 /// 版本控制 18 /// </summary> 19 public virtual int Version { get; set; } 20 /// <summary> 21 /// 一对多关系:一个Customer有一个或者多个Order 22 /// </summary> 23 public virtual System.Collections.Generic.ISet<Order> Orders { set; get; } 24 }
修改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 }
在nhibernate中,可以通过映射文件来关联对象之间的关系。
- 对象之间的关系:一对一,多对一,一对多,多对多关系。
- 在关系中控制级联行为(Cascade behavior):级联删除,级联更新。
- 父子关系的双向导(bidirectional navigation)
父实体映射
父实体Customer映射文件中,定义了:
集合类型:Set,Bag,List,Map
在保存,更新,删除操作时的级联行为。
关联的控制方向:
inverse=“false”(默认)父实体负责维护关联关系。
inverse=“true”子实体负责维护关联关系。
与子实体的关系:一对多,多对一,多对多。
修改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 <class name="Wolfy.Shop.Domain.Entities.Customer,Wolfy.Shop.Domain" table="TB_Customer"> 5 <!--主键--> 6 <id name="CustomerID" type="Guid" unsaved-value="null"> 7 <column name="CustomerID" sql-type="uniqueidentifier" not-null="true" unique="true"/> 8 <generator class="assigned"></generator> 9 </id> 10 <!--版本控制--> 11 <version name="Version" column="Version" type="integer" unsaved-value="0"/> 12 <!--组件 name组件属性名--> 13 <component name="NameAddress" class="Wolfy.Shop.Domain.Entities.Name,Wolfy.Shop.Domain"> 14 <!--Name类中的属性property--> 15 <property name="CustomerName" column ="CustomerName" type="string" 16 length="16" not-null="false" /> 17 <property name ="CustomerAddress" column="CustomerAddress" type="string" 18 length="128" not-null="false" /> 19 </component> 20 <!--一对多关系:一个客户可以有一个或者多个订单--> 21 <!--子实体负责维护关联关系--> 22 <set name="Orders" table="TB_Order" generic="true" inverse="true"> 23 <key column="CustomerID" foreign-key="FK_TB_Order_TB_Customer"></key> 24 <one-to-many class="Wolfy.Shop.Domain.Entities.Order,Wolfy.Shop.Domain"/> 25 </set> 26 </class> 27 </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>
many-to-one节点属性介绍:
子实体(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为外键字段生成一个唯一约束。
编写OrderData.cs代码,添加Order。
1 /// <summary> 2 /// 描述:订单数据层 3 /// 创建人:wolfy 4 /// 创建时间:2014-11-02 5 /// </summary> 6 public class OrderData 7 { 8 /// <summary> 9 /// 添加订单 10 /// </summary> 11 /// <param name="order"></param> 12 /// <returns></returns> 13 public bool AddOrder(Order order) 14 { 15 try 16 { 17 NHibernateHelper nhibernateHelper = new NHibernateHelper(); 18 var session = nhibernateHelper.GetSession(); 19 session.SaveOrUpdate(order); 20 session.Flush(); 21 return true; 22 } 23 catch (Exception) 24 { 25 throw; 26 } 27 } 28 }
测试页面AddCustomer.aspx
1 <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="CustomerManager.aspx.cs" Inherits="Wolfy.Shop.WebSite.CustomerManager" %> 2 3 <!DOCTYPE html> 4 5 <html xmlns="http://www.w3.org/1999/xhtml"> 6 <head runat="server"> 7 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 8 <title></title> 9 <style type="text/css"> 10 .main { 11 border: 1px solid #0094ff; 12 margin: 50px auto; 13 width: 600px; 14 } 15 16 .table { 17 border: 1px solid #0094ff; 18 border-collapse: collapse; 19 width: 98%; 20 text-align: center; 21 margin: 5px auto; 22 } 23 24 .table th { 25 background-color: lightgray; 26 } 27 28 .table tr td { 29 border: 1px solid #0094ff; 30 } 31 </style> 32 </head> 33 <body> 34 <form id="form1" runat="server"> 35 <div class="main"> 36 <asp:Button runat="server" ID="btnAdd" Text="添加" OnClick="btnAdd_Click" /><asp:Button Text="并发更新" ID="btnSameTimeUpdate" runat="server" OnClick="btnSameTimeUpdate_Click" /><br /> 37 按姓名查询: 38 <asp:TextBox runat="server" ID="txtName" /> 39 <asp:TextBox runat="server" ID="txtAddress" /> 40 <asp:Button Text="查询" runat="server" ID="btnSearch" OnClick="btnSearch_Click" /> 41 42 <div> 43 <asp:Repeater runat="server" ID="rptCustomerList"> 44 <HeaderTemplate> 45 <table class="table"> 46 <tr> 47 <th>姓名</th> 48 <th>姓名</th> 49 <th>地址</th> 50 <th>版本</th> 51 <th>姓名/地址</th> 52 <th>操作</th> 53 </tr> 54 </HeaderTemplate> 55 <ItemTemplate> 56 <tr> 57 <td><%#Container.ItemIndex+1 %></td> 58 <td><%#Eval("NameAddress.CustomerName") %></td> 59 <td><%#Eval("NameAddress.CustomerAddress") %></td> 60 <td><%#Eval("Version") %></td> 61 <td><%#Eval("NameAddress.NameAddress") %></td> 62 <td> 63 <asp:LinkButton runat="server" ID="lnkOrder" CommandArgument='<%#Eval("CustomerID") %>' CommandName="Order" OnClick="lnkOrder_Click">下单</asp:LinkButton></td> 64 </tr> 65 </ItemTemplate> 66 <FooterTemplate> 67 </table> 68 </FooterTemplate> 69 </asp:Repeater> 70 </div> 71 </div> 72 </form> 73 </body> 74 </html>