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的朋友分享经验。

二、开发环境与工具

先介绍一下这个实例的开发环境与工具:
  • NHibernate 3.2 Alpha2 :下载地址 http://sourceforge.net/projects/nhibernate/files/NHibernate/3.2.0Alpha2/
  • Visual Studio 2010
  • .NET Framework 4.0
  • NUnit 2.5.10
  • SQL Server 2005

三、实例场景

为便于理解与掌握,举个故意简化的实例:客户(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 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 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 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() 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 => c.Name == customerName”,这样就消除了ICriteria的字段名字符串硬编码的问题;
  • 返回单个值调用SingleOrDefault(),若查询结果不唯一则抛出异常NHibernate.NonUniqueResultException。
(2)根据客户地址查找多个客户
1 public IEnumerable GetByAddress(string address) 2 { 3 IEnumerable list = null; 4 5 using (var session = NHibernateSession) 6 using (var transaction = session.BeginTransaction()) 7 { 8 list = session.QueryOver() 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_ WHERE this_.Address = @p0;@p0 = 'Address' [Type: String (100)]
代码说明:
  • 查询多条数据调用List()方法。
(3)根据客户姓名模糊查找客户
1 public IEnumerable GetByLikeName(string likeName) 2 { 3 IEnumerable list = null; 4 5 using (var session = NHibernateSession) 6 using (var transaction = session.BeginTransaction()) 7 { 8 list = session.QueryOver() 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 GetByAmount(decimal minAmount, decimal maxAmount) 3 { 4 IEnumerable list = null; 5 6 using (var session = NHibernateSession) 7 using (var transaction = session.BeginTransaction()) 8 { 9 list = session.QueryOver() 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 GetByCustomerName(string customerName) 2 { 3 IEnumerable list = null; 4 5 using (var session = NHibernateSession) 6 using (var transaction = session.BeginTransaction()) 7 { 8 list = session.QueryOver() 9 .OrderBy(o=>o.Amount).Desc 10 .Inner.JoinQueryOver(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 GetByCustomerNameViaAlias(string customerName) 2 { 3 //定义用于内连接的别名变量,该变量必须赋值为null 4 Customer customer = null; 5 6 IEnumerable list = null; 7 8 using (var session = NHibernateSession) 9 using (var transaction = session.BeginTransaction()) 10 { 11 list = session.QueryOver() 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项目中的,可根据自己的运行环境进行相应的配置。
Tags: 

延伸阅读

最新评论

发表评论