NHibernate学习笔记(2)—关系映射
2009-10-05 22:21 小郝(Kaibo Hao) 阅读(1126) 评论(0) 编辑 收藏 举报NHibernate中的集合类型
NHibernate支持/定义的几种类型的集合:
Bag:对象集合,每个元素可以重复。例如{1,2,2,6,0,0},在.Net中相当于IList或者IList<T>实现。
Set:对象集合,每个元素必须唯一。例如{1,2,5,6},在.Net中相当于ISet或者ISet<T>实现,Iesi.Collections.dll程序集提供ISet集合。
List:整数索引对象集合,每个元素可以重复。例如{{1,"YJingLee"},{2,"CnBlogs"},{3,"LiYongJing"}},在.Net中相当于ArraryList或者List<T>实现。
Map:键值对集合。例如{{"YJingLee",5},{"CnBlogs",7},{"LiYongJing",6}},在.Net中相当于HashTable或者IDictionary<Tkey,TValue>实现。
实际上,我们大多数情况下使用Set集合类型,因为这个类型和关系型数据库模型比较接近。
父子关联映射
在NHibernate中,我们可以通过映射文件来关联对象之间的关系。映射文件定义了:
- 对象之间关系:一对一、一对多、多对一、多对多关系。
- 在关系中控制级联行为(Cascade behavior):级联更新、级联删除
- 父子之间的双向导航(bidirectional navigation)
1.父实体映射
父实体(Customer)映射定义了:
- 集合类型(Bag、Set、List、Map)
- 在保存、更新、删除操作时的级联行为
- 关联的控制方向:
- Inverse="false"(默认):父实体负责维护关联关系
- Inverse="true":子实体负责维护关联关系
- 与子实体关联的关系(一对多、多对一、多对多)
这些具体的设置是NHibernate中的难点所在,以后慢慢讨论这些不同设置下的奥秘之处。
这一篇初步建立Customer与Order的一对多关系,修改Customer.hbm.xml映射文件如下:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="DomainModel" namespace="DomainModel">
<class name ="DomainModel.Entities.Customer,DomainModel"
table="Customer">
<id name="CustomerId" column="CustomerId" type="Int32"
unsaved-value="0">
<generator class ="native"></generator>
</id>
<property name="Firstname" column ="Firstname" type="string"
length="50" not-null="false"/>
<property name ="Lastname" column="Lastname" type="string"
length="50" not-null="false"/>
<!--一对多关系:Customer有一个或多个Orders-->
<set name="Orders" table="`Order`" generic="true" inverse="true">
<key column="Customer" foreign-key="FK_CustomerOrders"/>
<one-to-many class="DomainModel.Entities.Order,DomainModel"/>
</set>
</class>
</hibernate-mapping>
可以看到,在“父”端映射使用Set元素,标明属性名称、表名、子实体负责维护关联关系。
2.子实体映射
子实体(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为外键字段生成一个唯一约束。
我们来建立“子”端到“父”端的映射,新建Order.hbm.xml文件,编写代码如下:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="DomainModel" namespace="DomainModel">
<class name="DomainModel.Entities.Order,DomainModel" table="`Order`" >
<id name="OrderId" column="OrderId" type="Int32" unsaved-value="0">
<generator class="native" />
</id>
<property name="OrderDate" column="OrderDate" type="DateTime"
not-null="true" />
<!--多对一关系:Orders属于一个Customer-->
<many-to-one name="Customer" column="Customer" not-null="true"
class="DomainModel.Entities.Customer,DomainModel"
foreign-key="FK_CustomerOrders" />
</class>
</hibernate-mapping>
关于如何关联看看上面的属性就一目了然了。
预过滤
使用ICriteria接口的SetResultTransformer(IResultTransformer resultTransformer)方法返回满足特定条件的Customer。上面例子中使用条件查询,观察其生成的SQL语句并没有 distinct,这时可以使用NHibernate.Transform命名空间中的方法或者使用NHibernate提供的 NHibernate.CriteriaUtil.RootEntity、 NHibernate.CriteriaUtil.DistinctRootEntity、 NHibernate.CriteriaUtil.AliasToEntityMap静态方法实现预过滤的作用。那么上面的查询应该修改为:
public IList<Customer> UseCriteriaAPI_GetCustomersWithOrders(DateTime orderDate)
{
return _session.CreateCriteria(typeof(Customer))
.CreateCriteria("Orders")
.Add(Restrictions.Gt("OrderDate", orderDate))
.SetResultTransformer(new NHibernate.Transform.DistinctRootEntityResultTransformer())
//或者.SetResultTransformer(NHibernate.CriteriaUtil.DistinctRootEntity)
.List<Customer>();
}
这个例子从转换结果集的角度实现了我们想要的效果。
对于不太理解的部分我框选了起来。
1. 为什么要查询两次,明明一次SQL查询可以解决的问题却要查询两次。也许是种扩展是为了说明投影而说投影么?还是有什么别的原因?
下面是我执行投影查询时检测到的SQL:
NHibernate: SELECT distinct this_.CustomerId as y0_ FROM Customer this_ inner join [Order] order1_ on this_.CustomerId=order1_.Customer WHERE order1_.OrderDate > @p0;@p0 = 2008-10-1 0:00:00
NHibernate: SELECT this_.CustomerId as CustomerId0_0_, this_.Version as Version0_0_, this_.Firstname as Firstname0_0_, this_.Lastname as Lastname0_0_ FROM Customer this_ WHERE this_.CustomerId in (@p0);@p0 = 1
NHibernate: SELECT orders0_.Customer as Customer1_, orders0_.OrderId as OrderId1_, orders0_.OrderId as OrderId1_0_, orders0_.OrderDate as OrderDate1_0_, orders0_.Customer as Customer1_0_ FROM [Order] orders0_ WHERE orders0_.Customer=@p0;@p0 = 1
其与代码执行的对应关系,据我分析应该如下:
多对多映射关系
1.Order有多个Products
修改Order.cs类代码如下:
namespace DomainModel.Entities
{
public class Order
{
public virtual int OrderId { get; set; }
public virtual DateTime OrderDate { get; set; }
//多对一关系:Orders属于一个Customer
public virtual Customer Customer { get; set; }
//多对多关系:Order有多个Products
public virtual IList<Product> Products { get; set; }
}
}
修改Order.hbm.xml映射文件如下:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="DomainModel" namespace="DomainModel">
<class name="DomainModel.Entities.Order,DomainModel" table="`Order`" >
<id name="OrderId" column="OrderId" type="Int32" unsaved-value="0">
<generator class="native" />
</id>
<property name="OrderDate" column="OrderDate" type="DateTime" not-null="true" />
<!--多对一关系:Orders属于一个Customer-->
<many-to-one name="Customer" column="Customer" not-null="true"
class="DomainModel.Entities.Customer,DomainModel"
foreign-key="FK_CustomerOrders" />
<!--多对多关系:Order有多个Products-->
<bag name="Products" generic="true" table="OrderProduct">
<key column="`Order`" foreign-key="FK_OrderProducts"/>
<many-to-many column="Product"
class ="DomainModel.Entities.Product,DomainModel"
foreign-key="FK_ProductOrders"/>
</bag>
</class>
</hibernate-mapping>
在多对多关系中,其两方都使用Bag集合和many-to-many元素。看看上面各个属性和one-to-many,many-to-one属性差不多。
2.Product属于多个Orders
在项目DomainModel层的Entities文件夹中新建Product.cs类,编写代码如下:
namespace DomainModel.Entities
{
public class Product
{
public virtual int ProductId { get; set; }
public virtual string Name { get; set; }
public virtual float Cost { get; set; }
//多对多关系:Product属于多个Orders
public virtual IList<Order> Orders { get; set; }
}
}
在项目DomainModel层的Mappings文件夹中新建Product.hbm.xml映射文件,编写代码如下:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="DomainModel" namespace="DomainModel">
<class name="DomainModel.Entities.Product,DomainModel" table="Product">
<id name="ProductId" column ="ProductId" type="Int32" unsaved-value="0">
<generator class="native"/>
</id>
<property name="Name" column="Name" type="string" not-null="true" length="50"/>
<property name="Cost" column="Cost" type="float" not-null="true"/>
<!--多对多关系:Product属于多个Orders-->
<bag name="Orders" generic="true" table="OrderProduct">
<key column="Product" foreign-key="FK_ProductOrders"/>
<many-to-many column="`Order`"
class="DomainModel.Entities.Order,DomainModel"
foreign-key="FK_OrderProducts"/>
</bag>
</class>
</hibernate-mapping>
使用NHibernate中提供的三种查询方法实现多对多关联查询,查询返回所有订单和产品的顾客列表。
1.原生SQL关联查询
public IList<Customer> UseSQL_GetCustomersWithOrdersHavingProduct(DateTime orderDate)
{
return _session.CreateSQLQuery("select distinct {customer.*} from Customer {customer}" +
" inner join [Order] o on o.Customer={customer}.CustomerId"+
" inner join OrderProduct op on o.OrderId=op.[Order]"+
" inner join Product p on op.Product=p.ProductId where o.OrderDate> :orderDate")
.AddEntity("customer", typeof(Customer))
.SetDateTime("orderDate", orderDate)
.List<Customer>();
}
这里需要使用Join告诉查询如何在表之间关联。无论多么复杂的关系,我们必须在查询语句中指定返回值。这里使用AddEntity设置返回的实体。
2.HQL关联查询
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>();
}
因为在映射文件已经定义实体之间一对多、多对多关系,NHibernate通过映射文件知道如何去关联这些实体,我们不需要在查询语句中重复定义。这里使用查询和上一篇使用HQL关联查询语句一样,NHibernate完全可以去关联对象,实现查询订单和产品。
3.Criteria API关联查询
因为实体之间的关联我们在映射文件中已经定义好了。所以我们在查询子对象使用子CreateCriteria语句关联对象之间导航,可以很容易地在 实体之间指定约束。这里第二个CreateCriteria()返回ICriteria的新实例,并指向Orders实体的元素。第三个指向 Products实体的元素。
public IList<Customer> UseCriteriaAPI_GetCustomerswithOrdersHavingProduct()对于多对多关联的查询对应的SQL语句如下:
{
return _session.CreateCriteria(typeof(Customer))
.Add(Restrictions.Eq("Firstname","YJing"))
.CreateCriteria("Orders")
.Add(Restrictions.Gt("OrderDate",new DateTime(2008,10,1)))
.CreateCriteria("Products")
.Add(Restrictions.Eq("Name","Cnblogs"))
.List<Customer>();
}
------ Test started: Assembly: DALTest.dll ------
NHibernate: SELECT this_.CustomerId as CustomerId0_2_, this_.Version as Version0_2_, this_.Firstname as Firstname0_2_, this_.Lastname as Lastname0_2_, order1_.OrderId as OrderId1_0_, order1_.OrderDate as OrderDate1_0_, order1_.Customer as Customer1_0_, products5_.[Order] as column1_, product2_.ProductId as Product, product2_.ProductId as ProductId3_1_, product2_.Name as Name3_1_, product2_.Cost as Cost3_1_ FROM Customer this_ inner join [Order] order1_ on this_.CustomerId=order1_.Customer inner join OrderProduct products5_ on order1_.OrderId=products5_.[Order] inner join Product product2_ on products5_.Product=product2_.ProductId WHERE this_.Firstname = @p0 and order1_.OrderDate > @p1 and product2_.Name = @p2;@p0 = 'Kaibo', @p1 = 2008-10-1 0:00:00, @p2 = 'Cnblogs'
NHibernate: SELECT orders0_.Customer as Customer1_, orders0_.OrderId as OrderId1_, orders0_.OrderId as OrderId1_0_, orders0_.OrderDate as OrderDate1_0_, orders0_.Customer as Customer1_0_ FROM [Order] orders0_ WHERE orders0_.Customer=@p0;@p0 = 1
NHibernate: SELECT products0_.[Order] as column1_1_, products0_.Product as Product1_, product1_.ProductId as ProductId3_0_, product1_.Name as Name3_0_, product1_.Cost as Cost3_0_ FROM OrderProduct products0_ left outer join Product product1_ on products0_.Product=product1_.ProductId WHERE products0_.[Order]=@p0;@p0 = 1
NHibernate: SELECT products0_.[Order] as column1_1_, products0_.Product as Product1_, product1_.ProductId as ProductId3_0_, product1_.Name as Name3_0_, product1_.Cost as Cost3_0_ FROM OrderProduct products0_ left outer join Product product1_ on products0_.Product=product1_.ProductId WHERE products0_.[Order]=@p0;@p0 = 2
另外还有一个需要注意的地方:对于数据库的关键字如:“Order” 在使用为字段名时,在配置文件中要对该“Column”配置加上特殊转义标记,以示区别,否则会报sql语法错误。
如:以下是本篇的相关代码:NHibernateSample2.zip
参考资料:
NHibernate之旅(10):探索父子(一对多)关联查询
NHibernate之旅(11):探索多对多关系及其关联查询
本文部分内容摘录自:YJingLee's Blog
出处:http://www.cnblogs.com/haokaibo/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。