一、引言
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 => 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)
]
代码说明:
(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)
]
代码说明:
(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项目中的,可根据自己的运行环境进行相应的配置。