NHibernate 3.x新功能实践(一) QueryOver(上)

一、引言

    NHibernate3.0增加了一种新的查询API——QueryOver。QueryOver构建在NHibernate原有的ICriteria API之上,支持Lambda表达式与扩展方法,可编写类型安全的查询语句,这样就克服了ICriteria API字符串硬编码的弊端,可借助VS提供的智能提示方便代码输入,减少输入错误。同时可利用VS等重构功能自动更新因实体字段名更改而导致的查询语句的变更,方便代码构。本文主要介绍QueryOver的常见应用,并结合一个可运行的实例对各查询场景进行详尽的阐述。

  时间过得真快,离最近一次在博客园写文章都快2年时间了。这些年工作是忙不到尽头,很少有空闲的时间可以静下心来写博文,空闲时间有的话也看书、睡觉、跟新技术去了。不过有句名言说得好:时间就像女人的乳沟挤挤总会有的,呵呵。最近刚结掉一个项目,利用难得的空隙机会研究了一下QueryOver,基本上实践了项目中的大部分查询场景,并计划在后面的新项目中使用QueryOver。

      只要熟悉ICriteria API与LINQ,玩玩QueryOver并不难,如果有个现成的详实例子就会节省不少学习时间,但是网上关于QueryOver的可运行的实例很少,再加上NHibernate在线帮助文档对QueryOver的介绍一如既往的简洁,所以就做了个实例来介绍QueryOver,并在文章最后提供源代码下载,以给刚接触QueryOver的朋友分享经验。

二、开发环境与工具

      先介绍一下这个实例的开发环境与工具:

三、实例场景

      为便于理解与掌握,举个故意简化的实例:客户(Customer)与订单(Order),一个客户可以下多个订单。实体类的代码如下: 

(1)实体基类: Entity

 1  using  System;
 2  using  System.Collections.Generic;
 3  using  System.Linq;
 4  using  System.Text;
 5 
 6  namespace  MyWorkShop.Model.Entities
 7  {
 8       public   abstract   class  Entity < TId >
 9      {
10           public   virtual  TId Id {  get protected   set ; }
11      }
12  }

  (2)客户类:Customer,映射的数据表为MyWorkShop_Customer

 1  using  System;
 2  using  System.Collections.Generic;
 3  using  System.Linq;
 4  using  System.Text;
 5 
 6  namespace  MyWorkShop.Model.Entities
 7  {
 8       public   class  Customer:Entity < int >
 9      {
10           public   virtual   string  Name {  get set ; }
11           public   virtual   string  Address {  get set ; }
12           public   virtual   string  Phone {  get set ; }
13      }
14  }

 (3)订单类:Order,映射的数据表为MyWorkShop_Order

 1  using  System;
 2  using  System.Collections.Generic;
 3  using  System.Linq;
 4  using  System.Text;
 5 
 6  namespace  MyWorkShop.Model.Entities
 7  {
 8       // Guid做主键
 9       public   class  Order : Entity < Guid >
10      {
11           // 下单客户
12           public   virtual  Customer Customer {  get set ; }
13           // 下单时间
14           public   virtual  DateTime OrderedDateTime {  get set ; }
15           // 订单金额
16           public   virtual  Decimal ?  Amount {  get set ; } 
17      }
18  }

 四、查询场景

 1. 筛选数据(Restriction)

 (1)根据客户姓名查找客户,假设客户姓名唯一

 1  public  Customer GetByName( string  customerName)
 2  {
 3      Customer entity  =   null ;
 4              
 5       using  (var session  =  NHibernateSession)
 6       using  (var transaction  =  session.BeginTransaction())
 7      {
 8         
 9          entity  =  session.QueryOver < Customer > ()
10              . Where (c  =>  c.Name  ==  customerName)
11              . SingleOrDefault ();
12 
13          transaction.Commit();
14      }
15       return  entity;
16  }

输出的SQL:

SELECT  this_.Id  as  Id5_0_, this_.Name  as  Name5_0_, this_.Address  as  Address5_0_, this_.Phone  as  Phone5_0_  FROM  MyWorkShop_Customer this_  WHERE  this_.Name  =   @p0 ; @p0   =   ' Name '   [ Type: String (50) ]

代码说明:

  • 筛选条件调用Where方法,使用Lambda表达式“=> c.Name == customerName”,这样就消除了ICriteria的字段名字符串硬编码的问题;
  • 返回单个值调用SingleOrDefault(),若查询结果不唯一则抛出异常NHibernate.NonUniqueResultException。

(2)根据客户地址查找多个客户

 1  public  IEnumerable < Customer >  GetByAddress( string  address)
 2  {
 3      IEnumerable < Customer >  list  =   null ;
 4 
 5       using  (var session  =  NHibernateSession)
 6       using  (var transaction  =  session.BeginTransaction())
 7      {
 8          list  =  session.QueryOver < Customer > ()
 9              .Where(c  =>  c.Address  ==  address)
10              . List ();
11 
12          transaction.Commit();
13      }
14 
15       return  list;
16  }

 输出的SQL:

SELECT  this_.Id  as  Id5_0_, this_.Name  as  Name5_0_, this_.Address  as  Address5_0_, this_.Phone  as  Phone5_0_  FROM  MyWorkShop_Customer this_  W HERE  this_.Address  =   @p0 ; @p0   =   ' Address '   [ Type: String (100) ]

代码说明:

  •  查询多条数据调用List()方法。

 (3)根据客户姓名模糊查找客户

 1  public  IEnumerable < Customer >  GetByLikeName( string  likeName)
 2  {
 3      IEnumerable < Customer >  list  =   null ;
 4 
 5       using  (var session  =  NHibernateSession)
 6       using  (var transaction  =  session.BeginTransaction())
 7      {
 8          list  =  session.QueryOver < Customer > ()
 9              . WhereRestrictionOn (o  =>  o.Name). IsLike (likeName, MatchMode.Anywhere)
10              .List();
11 
12          transaction.Commit();
13      }
14 
15       return  list;
16  }

输出的SQL:

SELECT  this_.Id  as  Id5_0_, this_.Name  as  Name5_0_, this_.Address  as  Address5_0_, this_.Phone  as  Phone5_0_  FROM  MyWorkShop_Customer this_  WHERE  this_.Name  like   @p0 ; @p0   =   ' %e% '   [ Type: String (50) ]

 代码说明:

  •  对于某些SQL函数与操作符(比如like、between...and...),没有直接对应的Lambda表达式,需要先使用WhereRestrictionOn方法指定筛选条件的列,然后再调用相应的方法指定筛选条件;
  • IsLike方法指定字符串匹配查找。

 (4)查找金额在指定范围内的订单

2  public  IEnumerable < Order >  GetByAmount( decimal  minAmount,  decimal  maxAmount)
 3  {
 4      IEnumerable < Order >  list  =   null ;
 5 
 6       using  (var session  =  NHibernateSession)
 7       using  (var transaction  =  session.BeginTransaction())
 8      {
 9          list  =  session.QueryOver < Order > ()
10              . Where (o  =>  o.Amount  >=  minAmount)
11              . And (o  =>  o.Amount  <=  maxAmount)
12              . OrderBy (o  =>  o.Amount). Desc
13              .List();
14 
15          transaction.Commit();
16      }
17 
18       return  list;
19  }

 输出的SQL:

SELECT  this_.Id  as  Id8_0_, this_.CustomerId  as  CustomerId8_0_, this_.OrderedDateTime  as  OrderedD3_8_0_, this_.Amount  as  Amount8_0_  FROM  MyWorkShop_Order this_  WHERE  this_.Amount  >=   @p0   and  this_.Amount  <=   @p1   ORDER   BY  this_.Amount  desc ; @p0   =   100   [ Type: Decimal (0) ] @p1   =   200   [ Type: Decimal (0) ]

 代码说明:

  • 多个条件可使用Where...And...逐个指定,也可以在一个Where方法中指定,比如上面的条件可以写成Where(o => o.Amount >= minAmount && o.Amount <= maxAmount);
  • 排序使用OrderBy,升序降序使用Asc与Desc。

2.连接(Join)

(1)内连接:根据客户姓名查找订单

 1  public  IEnumerable < Order >  GetByCustomerName( string  customerName)
 2  {
 3      IEnumerable < Order >  list  =   null ;
 4 
 5       using  (var session  =  NHibernateSession)
 6       using  (var transaction  =  session.BeginTransaction())
 7      {
 8          list  =  session.QueryOver < Order > ()
 9              .OrderBy(o => o.Amount).Desc
10              . Inner. JoinQueryOver < Customer > (o  =>  o.Customer)
11              .Where(c  =>  c.Name  ==  customerName)            
12              .List();
13 
14          transaction.Commit();
15      }
16 
17       return  list;
18  }

 输出的SQL:

SELECT  this_.Id  as  Id8_1_, this_.CustomerId  as  CustomerId8_1_, this_.OrderedDateTime  as  OrderedD3_8_1_, this_.Amount  as  Amount8_1_, customer1_.Id  as  Id9_0_, customer1_.Name  as  Name9_0_, customer1_.Address  as  Address9_0_, customer1_.Phone  as  Phone9_0_  FROM   MyWorkShop_Order this_  inner   join  MyWorkShop_Customer customer1_  on  this_.CustomerId = customer1_.Id  WHERE  customer1_.Name  =   @p0   ORDER   BY  this_.Amount  desc ; @p0   =   ' Name '   [ Type: String (50) ]

 代码说明:

  • .Inner.JoinQueryOver指定内连接,如果省略Inner仅写JoinQueryOver默认就是内连接;
  • .Left、.Right则分别为左外连接、右外连接
(2)使用别名进行内连接:根据客户姓名查找订单
 1  public  IEnumerable < Order >  GetByCustomerNameViaAlias( string  customerName)
 2  {
 3       // 定义用于内连接的别名变量,该变量必须赋值为null
 4       Customer customer  =   null ;
 5 
 6      IEnumerable < Order >  list  =   null ;
 7 
 8       using  (var session  =  NHibernateSession)
 9       using  (var transaction  =  session.BeginTransaction())
10      {
11          list  =  session.QueryOver < Order > ()
12               .JoinAlias(o  =>  o.Customer, ()  =>  customer)  // 指定别名customer
13              .Where(()  =>   customer .Name  ==  customerName)
14              .List();
15 
16          transaction.Commit();
17      }
18 
19       return  list;
20  }
 输出的SQL:

       同上,略

 代码说明:
  •  可以通过.Inner.JoinQueryOver来显式进行内连接,也可以通过.JoinAlias创建连接别名进行连接;
  •  连接别名变量在QueryOver使用之前定义,并且必须赋null值。

五、总结     

      与ICriteria API相比,个人认为QueryOver并不见得会提高代码的可读性,但QueryOver解决了

ICriteria API字符串硬编码的问题,从而减少代码输入的错误,大大提高了代码重构的能力,因此用QueryOver取代ICriteria API是值得的。

       本文通过一个简单的实例,介绍了QueryOver进行条件筛选(Restriction)、连接(Join)等常见场景的应用,在下一篇文章中将介绍投影(Projection)、把投影结果转成DTO、分页、子查询(Subquery)等。

六、参考资料

 [1] NHibernate关于QueryOver的在线帮助文档

 [2] NHibernate 3.0 Cookbook    P130-137

七、实例源代码下载

      源代码下载

八、实例项目说明

1. 项目组织结构

  • MyWorkShop.Model项目:创建实体类与DTO;
  • MyWorkShop.Data项目:创建数据访问接口;
  • MyWorkShop.Data.NHibernate项目:定义数据访问的NHibernate实现,包含文中所列举的各查询方法;
  • MyWorkShop.Data.NHibernate.Test项目:对MyWorkShop.Data.NHibernate项目中各数据访问方法进行测试。

2. 测试数据库     

      测试数据库名为MyWorkShop,数据库文件位于db文件夹中,附加数据库文件即可;当然也可手工创建名为MyWorkShop的数据库。

3. NHibernate配置文件

      hibernate.cfg.xml位于MyWorkShop.Data.NHibernate.Test项目中的,可根据自己的运行环境进行相应的配置。


你可能感兴趣的:(Hibernate)