• [NHibernate]一对多关系(级联删除,级联添加)


    目录

    写在前面

    文档与系列文章

    一对多关系

    一个例子

    级联删除

    级联保存

    总结

    写在前面

    在前面的文章中,我们只使用了一个Customer类进行举例,而在客户、订单、产品中它们的关系,咱们并没有涉及,比如一个客户可以有一个或者多个订单,在数据库中变现为“主外键关系”,有时也喜欢称为“父子关系”。那么就让我们一起学习,在nhibernate中,是如何处理这种关系的吧?

    文档与系列文章

    [Nhibernate]体系结构

    [NHibernate]ISessionFactory配置

    [NHibernate]持久化类(Persistent Classes)

    [NHibernate]O/R Mapping基础

    [NHibernate]集合类(Collections)映射 

    [NHibernate]关联映射

    [NHibernate]Parent/Child

    [NHibernate]缓存(NHibernate.Caches)

    [NHibernate]NHibernate.Tool.hbm2net

    [NHibernate]Nullables

    [NHibernate]Nhibernate如何映射sqlserver中image字段

    [NHibernate]基本配置与测试 

    [NHibernate]HQL查询 

    [NHibernate]条件查询Criteria Query

    [NHibernate]增删改操作

    [NHibernate]事务

    [NHibernate]并发控制

    [NHibernate]组件之依赖对象

    一对多关系

    首先看一下数据表的关系。

    这里先使用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>
    View Code
      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Web;
      5 using System.Web.UI;
      6 using System.Web.UI.WebControls;
      7 using Wolfy.Shop.Domain.Entities;
      8 
      9 namespace Wolfy.Shop.WebSite
     10 {
     11     public partial class CustomerManager : System.Web.UI.Page
     12     {
     13         protected void Page_Load(object sender, EventArgs e)
     14         {
     15             if (!IsPostBack)
     16             {
     17                 RepeaterDataBind();
     18                 //List<Guid> lst = new Business.CustomerBusiness().GetAllCustomerID() as List<Guid>;
     19                 //foreach (var item in lst)
     20                 //{
     21                 //    Response.Write(item.ToString());
     22                 //}
     23                 //  Response.Write(new Business.CustomerBusiness().GetCustomerByName("wolfy").FirstOrDefault().CustomerName);
     24             }
     25         }
     26         private void RepeaterDataBind()
     27         {
     28             Business.CustomerBusiness customerBusiness = new Business.CustomerBusiness();
     29             this.rptCustomerList.DataSource = customerBusiness.GetCustomerList(c => 1 == 1); //customerBusiness.GetCustomers();
     30             this.rptCustomerList.DataBind();
     31         }
     32         /// <summary>
     33         /// 添加客户信息
     34         /// </summary>
     35         /// <param name="sender"></param>
     36         /// <param name="e"></param>
     37         protected void btnAdd_Click(object sender, EventArgs e)
     38         {
     39             Guid guidCustomerID = Guid.NewGuid();
     40             var customer = new Customer() { NameAddress = new Name() { CustomerName = "zhangsan3322", CustomerAddress = "北京 海淀" }, CustomerID = guidCustomerID };
     41             Business.CustomerBusiness customerBusiness = new Business.CustomerBusiness();
     42             //使用事务的方式添加客户信息
     43             if (customerBusiness.SaveOrUpdateByTrans(customer))
     44             {
     45                 RepeaterDataBind();
     46             }
     47             //提供一个名字长度溢出的测试数据
     48             customer = new Customer() { NameAddress = new Name() { CustomerName = "zhangsan3322", CustomerAddress = "北京 海淀" }, CustomerID = Guid.NewGuid() };
     49             //使用事务的方式添加客户信息
     50             if (customerBusiness.SaveOrUpdateByTrans(customer))
     51             {
     52                 RepeaterDataBind();
     53             }
     54         }
     55         /// <summary>
     56         /// 查询
     57         /// </summary>
     58         /// <param name="sender"></param>
     59         /// <param name="e"></param>
     60         protected void btnSearch_Click(object sender, EventArgs e)
     61         {
     62 
     63             this.rptCustomerList.DataSource = new Business.CustomerBusiness().GetCustomerAddress(this.txtName.Text, this.txtAddress.Text);
     64             this.rptCustomerList.DataBind();
     65         }
     66         /// <summary>
     67         /// 并发更新操作
     68         /// </summary>
     69         /// <param name="sender"></param>
     70         /// <param name="e"></param>
     71         protected void btnSameTimeUpdate_Click(object sender, EventArgs e)
     72         {
     73             Guid guidCustomerId = new Guid("82724514-682E-4E6F-B759-02E499CDA50F");
     74             //模拟第一个修改数据
     75             Customer c1 = new Customer()
     76             {
     77                 CustomerID = guidCustomerId,
     78                 NameAddress = new Name() { CustomerName = "zhangsan3322", CustomerAddress = "北京 海淀" },
     79                 Version = 1
     80             };
     81             //模拟第二个修改数据
     82             Customer c2 = new Customer()
     83             {
     84                 CustomerID = guidCustomerId,
     85                 NameAddress = new Name() { CustomerName = "zhangsan3322", CustomerAddress = "北京 海淀" },
     86                 Version = 1
     87             };
     88 
     89             Business.CustomerBusiness customerBusiness = new Business.CustomerBusiness();
     90             customerBusiness.SaveOrUpdateByTrans(c1);
     91             customerBusiness.SaveOrUpdateByTrans(c2);
     92         }
     93         /// <summary>
     94         /// 下订单
     95         /// </summary>
     96         /// <param name="sender"></param>
     97         /// <param name="e"></param>
     98         protected void lnkOrder_Click(object sender, EventArgs e)
     99         {
    100             LinkButton lnkBtn = sender as LinkButton;
    101             if (lnkBtn!=null)
    102             {
    103                 Response.Redirect("AddOrder.aspx?cid="+lnkBtn.CommandArgument);
    104             }
    105         }
    106     }
    107 }
    View Code

    AddOrder.aspx.cs

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Web;
     5 using System.Web.UI;
     6 using System.Web.UI.WebControls;
     7 using Wolfy.Shop.Domain.Entities;
     8 
     9 namespace Wolfy.Shop.WebSite
    10 {
    11     public partial class AddOrder : System.Web.UI.Page
    12     {
    13         protected void Page_Load(object sender, EventArgs e)
    14         {
    15             if (!IsPostBack)
    16             {
    17                 string strCid = Request.QueryString["cid"];
    18                 if (!string.IsNullOrEmpty(strCid))
    19                 {
    20                     Business.OrderBusiness orderBusiness = new Business.OrderBusiness();
    21                     Business.CustomerBusiness customerBusiness = new Business.CustomerBusiness();
    22                     Order order = new Order() { Customer = customerBusiness.GetCustomerList(c => c.CustomerID == new Guid(strCid)).FirstOrDefault(), OrderDate = DateTime.Now, OrderID = Guid.NewGuid() };
    23                     if (orderBusiness.AddOrder(order))
    24                     {
    25                         Response.Write("添加成功");
    26                     }
    27                 }
    28             }
    29         }
    30     }
    31 }
    View Code

    添加订单生成的sql

    使用单例模式修改NHibernateHelper

     1     /// <summary>
     2     /// 描述:nhibernate辅助类
     3     /// 创建人:wolfy
     4     /// 创建时间:2014-10-16
     5     /// </summary>
     6     public class NHibernateHelper
     7     {
     8         private static ISessionFactory _sessionFactory;
     9         private static ISession _session;
    10         private static object _objLock = new object();
    11         private NHibernateHelper()
    12         {
    13 
    14         }
    15         /// <summary>
    16         /// 创建ISessionFactory
    17         /// </summary>
    18         /// <returns></returns>
    19         public static ISessionFactory GetSessionFactory()
    20         {
    21             if (_sessionFactory == null)
    22             {
    23                 lock (_objLock)
    24                 {
    25                     if (_sessionFactory == null)
    26                     {
    27                         //配置ISessionFactory
    28                         _sessionFactory = (new Configuration()).Configure().BuildSessionFactory();
    29                     }
    30                 }
    31             }
    32             return _sessionFactory;
    33 
    34         }
    35         /// <summary>
    36         /// 打开ISession
    37         /// </summary>
    38         /// <returns></returns>
    39         public static ISession GetSession()
    40         {
    41             _sessionFactory = GetSessionFactory();
    42             if (_session == null)
    43             {
    44                 lock (_objLock)
    45                 {
    46                     if (_session == null)
    47                     {
    48                         _session = _sessionFactory.OpenSession();
    49                     }
    50                 }
    51             }
    52             return _session;
    53         }
    54     }
    View Code

    不然有可能出现下面的异常
    Illegal attempt to associate a collection with two open sessions

    级联删除

     在级联删除过程中遇到的问题

    1,设置Customer.hbm.xml中set属性inverse=“true”,必须设置cascade属性

    1  <set name="Orders" table="TB_Order" generic="true" inverse="true" cascade="all" >
    2       <key column="CustomerID" foreign-key="FK_TB_Order_TB_Customer"></key>
    3       <one-to-many class="Wolfy.Shop.Domain.Entities.Order,Wolfy.Shop.Domain"/>
    4     </set>

    此时添加订单,生成的数据如下图所示

    当删除CustomerID=‘3042B445-D3AC-4CC0-BDFF-311982E4265A’的客户信息时。

    生成的sql语句

    1 exec sp_executesql N'DELETE FROM TB_Order WHERE OrderID = @p0',N'@p0 uniqueidentifier',@p0='574BFFF6-D4EC-4EFA-9DBD-32817CD922EC'
    2 exec sp_executesql N'DELETE FROM TB_Order WHERE OrderID = @p0',N'@p0 uniqueidentifier',@p0='355FF069-FD95-4F33-A6DF-D1AD90CB56D0'
    3 exec sp_executesql N'DELETE FROM TB_Customer WHERE CustomerID = @p0 AND Version = @p1',N'@p0 uniqueidentifier,@p1 int',@p0='3042B445-D3AC-4CC0-BDFF-311982E4265A',@p1=1

    如果不设置cascade属性,会产生如下异常

    因为inverse=“true”子实体负责维护关联关系。此时你删除Customer中的记录时,由于在Order表中有对应于该客户的订单信息,会违反外键约束。

    cascade(可选):指明哪些操作会从父对象级联到关联的对象。可选all、save-update、delete、none值。除none之外其它将使指定的操作延伸到关联的(子)对象。

    2、设置Customer.hbm.xml中set属性inverse=“false”时。

    inverse=“false”(默认)父实体负责维护关联关系。

    此时如果删除Customer,则属于该客户的订单,会将订单表中的Customerid置为null。

    删除CustomerID=‘B0387B7E-C9F3-4970-9429-955DDDC2ACAA’

    看一下生成的sql语句

    exec sp_executesql N'UPDATE TB_Order SET CustomerID = null WHERE CustomerID = @p0',N'@p0 uniqueidentifier',@p0='B0387B7E-C9F3-4970-9429-955DDDC2ACAA'
    exec sp_executesql N'DELETE FROM TB_Customer WHERE CustomerID = @p0 AND Version = @p1',N'@p0 uniqueidentifier,@p1 int',@p0='B0387B7E-C9F3-4970-9429-955DDDC2ACAA',@p1=1

    此时通过sql语句也可以看到,如果是父实体负责维护关联关系,会在子表中将外键置为null,如果你的外键不允许为null,肯定会有异常的。

    如果加上cascade="all"属性

    通过生成的sql语句,你会发现此时仍会修改外键为null,然后再去删除

    1 exec sp_executesql N'UPDATE TB_Order SET CustomerID = null WHERE CustomerID = @p0',N'@p0 uniqueidentifier',@p0='82724514-682E-4E6F-B759-02E499CDA50F'
    2 exec sp_executesql N'DELETE FROM TB_Order WHERE OrderID = @p0',N'@p0 uniqueidentifier',@p0='AD7EB390-70BC-4ECF-B593-15A24A40CD26'
    3 exec sp_executesql N'DELETE FROM TB_Order WHERE OrderID = @p0',N'@p0 uniqueidentifier',@p0='7D21E3FC-CDCF-4586-9714-D6B023CCBFA9'
    4 exec sp_executesql N'DELETE FROM TB_Customer WHERE CustomerID = @p0 AND Version = @p1',N'@p0 uniqueidentifier,@p1 int',@p0='82724514-682E-4E6F-B759-02E499CDA50F',@p1=2

    如果在注重效率的情况下,这中方式没有采用子实体维护关联关系的方式效率高。
    通过上面的分析,不管是父实体维护关联关系还是子实体维护关联关系,为映射set节点加上cascade属性是必须的。

    级联保存

     一个简单的例子,在添加客户的时候,直接为用户添加订单。

     1         /// <summary>
     2         /// 添加客户和订单
     3         /// </summary>
     4         /// <param name="sender"></param>
     5         /// <param name="e"></param>
     6         protected void btnCsOrder_Click(object sender, EventArgs e)
     7         {
     8             var customer = new Customer() { NameAddress = new Name() { CustomerName = "zhangsanOrder", CustomerAddress = "北京 海淀" }, CustomerID = Guid.NewGuid() };
     9             customer.Orders = new HashSet<Order>();
    10             customer.Orders.Add(new Order() { OrderID = Guid.NewGuid(), OrderDate = DateTime.Now, Customer = customer });
    11             customer.Orders.Add(new Order() { OrderID = Guid.NewGuid(), OrderDate = DateTime.Now, Customer = customer });
    12             Business.CustomerBusiness customerBusiness = new Business.CustomerBusiness();
    13             if (customerBusiness.AddCustomer(customer))
    14             {
    15                 this.RepeaterDataBind();
    16             }
    17         }

    此时的Customer.hbm.xml映射中Set节点的配置如下

        <!--一对多关系:一个客户可以有一个或者多个订单-->
        <!--子实体负责维护关联关系-->
        <set name="Orders" table="TB_Order" generic="true" inverse="false" cascade="all">
          <key column="CustomerID" foreign-key="FK_TB_Order_TB_Customer"></key>
          <one-to-many class="Wolfy.Shop.Domain.Entities.Order,Wolfy.Shop.Domain"/>
        </set>

    生成的sql语句为

    你会发现,添加的一个客户信息,对应添加两个Order,添加Order后会执行Update

    exec sp_executesql N'INSERT INTO TB_Customer (Version, CustomerName, CustomerAddress, CustomerID) VALUES (@p0, @p1, @p2, @p3)',N'@p0 int,@p1 nvarchar(4000),@p2 nvarchar(4000),@p3 uniqueidentifier',@p0=1,@p1=N'zhangsanOrder',@p2=N'北京 海淀',@p3='4E0C307A-E221-41D5-849D-C6E9F271BD36'
    exec sp_executesql N'INSERT INTO TB_Order (OrderDate, CustomerID, OrderID) VALUES (@p0, @p1, @p2)',N'@p0 datetime,@p1 uniqueidentifier,@p2 uniqueidentifier',@p0='2014-11-02 14:24:42',@p1='4E0C307A-E221-41D5-849D-C6E9F271BD36',@p2='E44DF3B5-BFD1-4C09-96B5-492D7CAEAADA'
    exec sp_executesql N'INSERT INTO TB_Order (OrderDate, CustomerID, OrderID) VALUES (@p0, @p1, @p2)',N'@p0 datetime,@p1 uniqueidentifier,@p2 uniqueidentifier',@p0='2014-11-02 14:24:42',@p1='4E0C307A-E221-41D5-849D-C6E9F271BD36',@p2='4E5524D3-48A9-4501-8A52-CD181B843608'
    exec sp_executesql N'UPDATE TB_Order SET CustomerID = @p0 WHERE OrderID = @p1',N'@p0 uniqueidentifier,@p1 uniqueidentifier',@p0='4E0C307A-E221-41D5-849D-C6E9F271BD36',@p1='E44DF3B5-BFD1-4C09-96B5-492D7CAEAADA'
    exec sp_executesql N'UPDATE TB_Order SET CustomerID = @p0 WHERE OrderID = @p1',N'@p0 uniqueidentifier,@p1 uniqueidentifier',@p0='4E0C307A-E221-41D5-849D-C6E9F271BD36',@p1='4E5524D3-48A9-4501-8A52-CD181B843608'

    这确实有点奇怪了,难道是为了在插入后通过修改外键,来保证这种外键约束吗?
    为了一探究竟,对比一下,inverse=true的情况

    1     <!--一对多关系:一个客户可以有一个或者多个订单-->
    2     <!--子实体负责维护关联关系-->
    3     <set name="Orders" table="TB_Order" generic="true" inverse="true" cascade="all">
    4       <key column="CustomerID" foreign-key="FK_TB_Order_TB_Customer"></key>
    5       <one-to-many class="Wolfy.Shop.Domain.Entities.Order,Wolfy.Shop.Domain"/>
    6     </set>

    此时生成的sql

    1 exec sp_executesql N'INSERT INTO TB_Customer (Version, CustomerName, CustomerAddress, CustomerID) VALUES (@p0, @p1, @p2, @p3)',N'@p0 int,@p1 nvarchar(4000),@p2 nvarchar(4000),@p3 uniqueidentifier',@p0=1,@p1=N'zhangsanOrder',@p2=N'北京 海淀',@p3='B0720295-9541-40B3-9994-610066224DB8'
    2 exec sp_executesql N'INSERT INTO TB_Order (OrderDate, CustomerID, OrderID) VALUES (@p0, @p1, @p2)',N'@p0 datetime,@p1 uniqueidentifier,@p2 uniqueidentifier',@p0='2014-11-02 14:33:09',@p1='B0720295-9541-40B3-9994-610066224DB8',@p2='78A53F67-A293-48A1-BBE2-86FED77342FA'
    3 exec sp_executesql N'INSERT INTO TB_Order (OrderDate, CustomerID, OrderID) VALUES (@p0, @p1, @p2)',N'@p0 datetime,@p1 uniqueidentifier,@p2 uniqueidentifier',@p0='2014-11-02 14:33:09',@p1='B0720295-9541-40B3-9994-610066224DB8',@p2='51CA6A37-EC79-4613-B8D7-CEEF5A4BB8EE'

    可以通过子实体来维护级联关系,更符合程序员的逻辑习惯,并且效率上更好一些。

    总结

    a)
    inverse="false"
    cascade="all"
    结果:Customer和Order都删了
    b)
    inverse="true"
    cascade="all"
    结果:Customer和Order都删了
    c)
    不设置inverse
    不设置cascade
    结果:Customer删除了,Order的Customer设为了空
    d)
    不设置inverse
    设置cascade ="delete"
    结果:Customer和Order都删了
    e)
    不设置inverse
    设置cascade ="save-update"
    结果:Customer删除了,Order的Customer设为了空
    f)
    不设置inverse
    设置cascade ="all-delete-orphan"
    结果:Customer和Order都删了

    通过上面的级联删除与添加分析,建议使用inverse="true" cascade="all"的情况,因为此时更符合咱们的思维方式,效率上也更好。

    参考文章

    http://www.cnblogs.com/lyj/archive/2008/10/24/1319052.html

    http://home.cnblogs.com/group/topic/39307.html

  • 相关阅读:
    SSM博客
    做完了第一个
    day08
    day07
    day06
    解决Zend OPcache huge_code_pages: mmap(HUGETLB) failed: Cannot allocate memory报错
    《响应式网页设计》系列分享专栏
    《Linux命令学习手册》系列分享专栏
    详解Docker 端口映射与容器互联
    centos7安装gitlab
  • 原文地址:https://www.cnblogs.com/wolf-sun/p/4068749.html
Copyright © 2020-2023  润新知