|
最近在使用NHibernate重构一个项目,好几处需要用到全文检索混搭其他一些条件进行的列表搜索。而大家都知道NHibernate本身是不支持全文检索的,网络上能找到的几处文章说来大概有以下几种方式
1. 修改hibernate.cfg.xml来扩展contains, freetext谓词到HQL, 参见:http://nhforge.org/blogs/nhibernate/archive/2009/03/13/registering-freetext-or-contains-functions-into-a-nhibernate-dialect.aspx
这种方法使得查询只能通过HQL进行,对于熟悉HQL的同学是个不错的选择,对于列表查询,获取列表总数的select count(*)和select *是两条HQL,需要写两次HQL的where条件或拼接HQL,因此个人不是很喜欢。
2. 为ICriteria查询添加SQL表达式参见:http://archive.iyunv.com/a/1289840/
这个方法很棒,也可以通过 criteria.setProjection(Projections.rowCount()).uniqueResult() 来直接获得记录总数,对CreateCriteria不熟悉的同学们要注意文中的Expression.Sql里的Expression不是System.Linq.Expressions.Expression, 而是 NHibernate.Criterion.Expression
3. 基于Lucene的第三方扩展 NHibernate.Search : http://www.iyunv.com/lonely7345/archive/2009/03/17/1413836.html
这个对项目的改动太大,而且手上的项目Sql Server自身的搜索就够用了,还没有复杂到要引入Lucene,因此也没有去具体了解。
4. 由于大量使用了Linq,因此自然希望全文搜索功能能够和Linq表达式无缝连接,因此这里介绍一个我自己折腾的Linq方法
先看下最终的调用方式:
var customers = Hibernate.Session.Query()
.Where
(
u => u.Company.Name.ContainsSearch("公司", u.Company.Phone)
);
这里的含义是在Company.Name和Company.Phone中查找指定的关键词,ContainsSearch方法最后的参数是params的,因此可以在多个字段中搜索
下面来看看实现过程:
首先参见了园子里YJingLee的一篇文章:NHibernate3剖析:Query篇之NHibernate.Linq自定义扩展
有了这个知识基础,我们现在只需要自定义一个Linq扩展方法,然后让NHibernate把这个扩展方法映射成contains
或者freetext就可以了,首先定义一个Linq扩展方法:
public static class MyHibernateLinqExtensions
{
///
/// 对字段进行模式搜索
///
///
/// 关键字
/// 其他用于搜索的字段
///
public static bool ContainsSearch(this string source, string keyword, params string[] otherProperties)
{
throw new NotSupportedException("仅用于数据库搜索");
}
}
查看HqlTreeBuilder,其中有个BooleanMethodCall可以帮忙:(以下是NHibernate的官方源码片段)
public HqlBooleanMethodCall BooleanMethodCall(string methodName, IEnumerable parameters)
{
return new HqlBooleanMethodCall(_factory, methodName, parameters);
}
我们看一下Sql Server中contains谓词的调用语法:
select * from customer
where contains((company, phone, address), '熊猫手机')
因此contains谓词的调用正是一个返回boolean类型的方法调用,参数 IEnumerable parameters是这个函数的参数的表达式形式列表
要注意的是contains和freetext其实只有两个参数,第一个参数的实现方式可以看成是一个方法调用的结果,只是这个方法的methodName = string.Empty
下面搬上整个扩展类
View Code
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Linq.Expressions;
5 using System.Text;
6 using NHibernate;
7 using NHibernate.Linq;
8 using NHibernate.Hql;
9 using NHibernate.Hql.Ast;
10 using NHibernate.Hql.Ast.ANTLR;
11
12 namespace Ares.Service.Linq
13 {
14 public static class MyHibernateLinqExtensions
15 {
16 ///
17 /// 对字段进行模式搜索
18 ///
19 ///
20 /// 关键字
21 /// 其他用于搜索的字段
22 ///
23 public static bool ContainsSearch(this string source, string keyword, params string[] otherProperties)
24 {
25 throw new NotSupportedException("仅用于数据库搜索");
26 }
27
28 ///
29 /// 对字段进行开放式搜索
30 ///
31 ///
32 /// 关键字
33 /// 其他用于搜索的字段
34 ///
35 public static bool FreeSearch(this string source, string keyword, params string[] otherFields)
36 {
37 throw new NotSupportedException("仅用于数据库搜索");
38 }
39 }
40
41 class ContainsSearchGenerator : NHibernate.Linq.Functions.BaseHqlGeneratorForMethod
42 {
43 public ContainsSearchGenerator()
44 {
45 SupportedMethods = new[]
46 {
47 ReflectionHelper.GetMethodDefinition
48 (
49 () => MyHibernateLinqExtensions.ContainsSearch(null, null, null)
50 )
51 };
52 }
53
54 public override NHibernate.Hql.Ast.HqlTreeNode BuildHql(System.Reflection.MethodInfo method, Expression targetObject, System.Collections.ObjectModel.ReadOnlyCollection arguments, NHibernate.Hql.Ast.HqlTreeBuilder treeBuilder, NHibernate.Linq.Visitors.IHqlExpressionVisitor visitor)
55 {
56 var pms = arguments.Select(p => visitor.Visit(p).AsExpression()).ToArray();
57
58 var fields = treeBuilder.MethodCall("", pms.Where(p => p != pms[1]).ToArray());
59
60 return treeBuilder.BooleanMethodCall("contains", new HqlExpression[] { fields, pms[1] });
61 }
62 }
63
64 class FreeSearchGenerator : NHibernate.Linq.Functions.BaseHqlGeneratorForMethod
65 {
66 public FreeSearchGenerator()
67 {
68 SupportedMethods = new[]
69 {
70 ReflectionHelper.GetMethodDefinition
71 (
72 () => MyHibernateLinqExtensions.FreeSearch(null, null, null)
73 )
74 };
75 }
76
77 public override NHibernate.Hql.Ast.HqlTreeNode BuildHql(System.Reflection.MethodInfo method, Expression targetObject, System.Collections.ObjectModel.ReadOnlyCollection arguments, NHibernate.Hql.Ast.HqlTreeBuilder treeBuilder, NHibernate.Linq.Visitors.IHqlExpressionVisitor visitor)
78 {
79 var pms = arguments.Select(p => visitor.Visit(p).AsExpression()).ToArray();
80
81 var fields = treeBuilder.MethodCall("", pms.Where(p => p != pms[1]).ToArray());
82
83 return treeBuilder.BooleanMethodCall("freetext", new HqlExpression[] { fields, pms[1] });
84 }
85 }
86
87 class MyLinqToHqlGeneratorRegistry : NHibernate.Linq.Functions.DefaultLinqToHqlGeneratorsRegistry
88 {
89 public MyLinqToHqlGeneratorRegistry()
90 {
91 RegisterGenerator(ReflectionHelper.GetMethodDefinition(() => MyHibernateLinqExtensions.ContainsSearch(null, null, null)), new ContainsSearchGenerator());
92 RegisterGenerator(ReflectionHelper.GetMethodDefinition(() => MyHibernateLinqExtensions.FreeSearch(null, null, null)), new FreeSearchGenerator());
93 }
94 }
95 }
注册这个扩展的方法(在实现Factory方法的地方):
private static ISessionFactory BuildFactory()
{
var cfg = new Configuration();
//大家实现SessionFactory的方法可能会略有区别,重要的是在BuildSessionFactory()之前,调用下面的方法将扩展类注册到你的Configuration对象即可
cfg.LinqToHqlGeneratorsRegistry();
cfg = cfg.Configure();
return cfg.BuildSessionFactory();
}
现在我们就可以将全文搜索和其他各种Linq方法一起混合调用了。 |
|
|