首页 > 代码库 > [Fluent NHibernate]一对多关系处理
[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 /// 创建人:wolfy13 /// 创建时间:2014-10-1614 /// </summary>15 public class Customer16 {17 /// <summary>18 /// 客户id19 /// </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有一个或者多个Order35 /// </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 /// 创建人:wolfy11 /// 创建时间:2014-10-1612 /// </summary>13 public class Order14 {15 /// <summary>16 /// 订单id17 /// </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.Mapping10 {11 /// <summary>12 /// 描述:订单实体映射类13 /// 创建人:wolfy14 /// 创建时间:2014-12-0715 /// </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可以属于一个customer27 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.Mapping10 {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 //自动增长的id25 //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|saveorUpdate33 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.Mapping10 {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 //自动增长的id25 //Id(i => i.CustomerID);26 //映射关系27 Id<Guid>("CustomerID").GeneratedBy.GuidComb();28 //指定主键后一定要加上主键字段的映射关系,不然返回的id为new Guid(),也就是一串029 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 relationship36 // 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 try12 {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之旅(四)-- 关系(下)
[Fluent NHibernate]一对多关系处理