代码改变世界

利用现有框架定制表达式树分析器

2011-07-27 15:28  PeterWang2004  阅读(369)  评论(0编辑  收藏  举报

  我们在做数据库应用程序时,为了业务的需要,常常把Where条件以及Orderby子句当作参数,传递到SQL语句或者是存储过程中。这样难免就会涉及到很多字符串操作。当我们的数据库字段发生改变时,我们就会改很多的代码,造成很多不必要的操作,而且,字符串拼接老是会出现一些琐屑问题,比如,SqlServer中,需要将字符串用单引号的方式包起来。用一个英文单词来描述上面的这些问题——"error-prone"。那么,该如何减轻这种痛苦呢?

  以前,我是这样考虑的,思想如下:将所有where条件句中的谓词对象化,然后使用Visitor模式,进行遍历生成对应的SQL语句,虽然用了它做了很久的程序,但还是发现它的表达形式太差,让一些新接触项目的同学摸不到头脑并且仍然避免不到修改数据库表字段发生的问题。到了.NET 3.5时代,由于出现了Linq,所以我就一直想用Linq provider来实现一个Where以及Orderby的解析器,奈何,当时还在读书,导师压得紧,完全没时间去考虑,如今,总算毕业了,可以认真的思考下这个问题了。

1.什么是现有框架

  现有框架是本次讨论的大前提,那么什么是现有框架呢?其实就是在业务层做的一个个Wrapper函数,定义原型是:

 public static List<TResult> GetList<TResult>(int pageIndex, int pageCount, string whereClause, string orderbyClause)
{
//............
}

  这个函数的参数whereClause和orderbyClause便是生成的SQL语句。最直接的调用便是这样:

GetList<Person>(pageIndex, 
pageCount
,
"where name="+name+" and address like '%"+address+"'%",
"order by name desc,address");

 首先这个函数是在业务层调用的,在业务层我们写了SQL代码,这明显不符合SRP原则,看起来也觉得很“丑”,如果我们的代码中到处都是这样的东西,以后代码维护起来就会变得相当的困难。 也许,我们可以用OO来进行一次封装,可是在语法方面还是有点表现不足。说到这里,就要谈到我们的Linq了。 

2.解决方案

  我开发的类库WhereOrderLinqProvider便解决了这两个问题。我已把代码注释写到程序中去了,由于本人语文水平不行(自从高中起语文从来就没及格过),所以有些文字表述方面可能会有些不清,望见谅。我希望能和大家一起交流,一起学习,如果你对这个类库有任何的建议都可以在本文后留言,下面我就来说说这个类库的简要使用。

2.1最简单的查询

       /// <summary>
/// 根据地址找到信息
/// </summary>
static IQueryable<Person> SimpleQuery()
{
Console.WriteLine(
"请输入地址:");
var address
= Console.ReadLine();
var scal
= new ScalSqlQueryble<Person>((where, order) => DataAccess.GetPerson(0, 0, where, order));
var queryble
= from s in scal
where s.Address==address
select s;
return queryble;
}

2.2 使用模糊查询

  在Linq中并没有提供像Sqlserver里面的like的模糊查询,但它确是广泛使用的一个谓词,为了解决这种问题,我创建了一个SqlFunctionHelper的类,用来为数据库提供一些内置函数。下面

是具体的应用。

 static IQueryable<Person> MultiConditionWithHelper()
{
Console.WriteLine(
"当前使用模糊查询,请输入地址:");
var address
= Console.ReadLine();
var scal
= new ScalSqlQueryble<Person>((where, order) => DataAccess.GetPerson(0, 0, where, order));
var sqlserverHelper
=(SqlServerFunctionHelper)(scal.Literal.CreateSqlHelper());
var queryble
= from s in scal
where sqlserverHelper.Like(() => s.Address, LikeWildcard.Percent, address, LikeWildcard.Percent)
select s;
return queryble;
}

2.3 扩展自己的SQL函数

  假设我提供的函数不能满足你的需要怎么办呢?你只需要写个类直接或者间接的继承自SqlFuncionHelper加入你自己的方法就行了,最后就可以在where中使用了。你可能会说这些函数能否在Order中使用,那是可以的,但需要你自己去扩展OrderFinder(我只是简单的将其设置为解析值s)。下面,我演示一个SqlServer中的IsNull函数。

 /// <summary>
/// 这个类为SqlServer提供了额外的自定义函数
/// </summary>
class NewSqlServer:SqlServerFunctionHelper
{
public NewSqlServer(SqlLiteral literal)
:
base(literal)
{
}


public bool IsNull(Expression<Func<object>> target, params object[] objs)
{
var targetString
= base.GetStringFromLambda(target, true);
this.SqlLiteral[SqlBlock.Where] = string.Format("{0} is null", targetString);
return true;
}
}

 这里面我为SqlServerFunctionHelper中提供了IsNull函数,请注意这里面的GetStringFromLambda函数,它是将target这个表达式树解析成对应的文字描述,比如target=(person)=>

person.Name则会解析成"Name"。更详细过程请见代码注释。最后,我们可以这样使用:

  static IQueryable<Person> ExtendQuery()
{
Console.WriteLine(
"请按回车查询为NULL的地址!");
Console.ReadKey();
var personQueryble
= new ScalSqlQueryble<Person>((where, order) => DataAccess.GetPerson(0, 0, where, order));
var sqlhelper
= new NewSqlServer(personQueryble.Literal);

var querybleValue
= from s in personQueryble
where sqlhelper.IsNull(() => s.Address)
select s;

return querybleValue;
}

  假如,你想迁移到Oracle或其它数据库,那么你需要同时实现SqlLiteal和SqlFunctionHelper类。

3.结语

 子句解析时间复杂度会随着子句的复杂程度增加而增加,所以尽量避免在子句中调用函数(你可以先将函数运行,而只在子句中传值)。在子句中,我做了一个约定,在上面这段代码中如果调用s的成员,类库则会抛出错误,但如果s的成员用在表达式的右端,则会返回它的成员名(比如我们可以这样写:s.Age=s.Age+1,那么解析成:age=age+1),具体的原因,我在下载的代码中详细介绍。

  我没有实现groupby子句,是因为我们这边的框架不需要它,如果你想它受支持,你需继承VisitorBase类,将对应的继承类放入职责链中即可。