zoukankan      html  css  js  c++  java
  • [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之旅(四)-- 关系(下)

  • 相关阅读:
    Python并行编程(十三):进程池和mpi4py模块
    Python 列表集合 字典推导式、生成器表达式
    Python 迭代对象、迭代器
    Python 参数,嵌套函数 的变量 使用
    Python 编码进阶
    Python 深浅Copy
    Python 代码块、缓存机制
    Python 列表,字典 相关方法
    初识 python 字符串 相关函数
    初识编码格式
  • 原文地址:https://www.cnblogs.com/wolf-sun/p/4149202.html
Copyright © 2011-2022 走看看