基于Expression Lambda表达式树的通用复杂动态查询构建器——《构思篇二》已开源
Posted ls0001
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于Expression Lambda表达式树的通用复杂动态查询构建器——《构思篇二》已开源相关的知识,希望对你有一定的参考价值。
接续[上篇之预告]
本篇来讲讲,如何根据前面设计的查询描述器构造出可执行的表达式。正如标题所示,实现手段将采用Expression Lambda技术。
先来看看主角System.Linq.Expressions.Expression
长什么样,都有些什么东西,能做什么。 先看看它的类图:
-
System.Linq.Expressions.MemberExpression 成员(属性或字段)
我们主要使用Expression
这个类,它包含各种节点类型的 static
工厂方法。特别是以下这些方法,对应了查询需要用到的逻辑、比较、数学,等各种运算操作。
以上仅列出了一部分,它涵盖了全部可能要使用到的函数,语句,欲详细了解可以直接乘坐火箭到微软官方网站查看。
这此函数或功能是有了,如何使用呢?来看个例子:将SQL
:Table1.A > 5 and Table1.B=3
,转成(Table1)=>Table1.A > 5 && Table1.B==3
;
在Expression
的世界里一切都是Expression
。因此首先要将Table1
、A
、5
、B
、 3
先包装成Expression
:
- 开干之前先谈个对象
//实体类:
public class Table
public int A;
public int B;
- 然后包装各种
Expression
//将Table1包装成ParameterExpression:
var p=Expression.Parameter(typeof(Table1),"Table1");
//将5、3这两个常量包装成ConstantExpression:
var num5=Expression.Constant(5,typeof(int));
var num3=Expression.Constant(3,typeof(int));
//将两个属性包装成MemberExpression。
var a=Expression.PropertyOrField(p,"A")
var b=Expression.PropertyOrField(p,"B")
//构造Table1.A>5:
var gt=Expression.GreaterThen(a,num5);
//构造Table1.A=3:
var eq=Expression.Equal(b,num3);
- 再构造两个比较式
//构造Table1.A>5:
var gt=Expression.GreaterThen(a,num5);
//构造Table1.A=3:
var eq=Expression.Equal(b,num3);
- 加上逻辑
And
将两个比较式连接起来
//构造Table1.A>5 && Table1.A=3
var exp=Expression.AndAlso(gt,eq);
- 结果就要出来了
//结果就获得了:
var lambda=Expression.Lambda()
- 来来来,测试一下康康接果:
/*===============来来来,测试一下:===============*/
var f=lambda.Compile();
var x=f.DynamicInvoke(new Table()A=6,B=3);
var y = f.DynamicInvoke(new Table() A = 2, B = 3 );
Console.WriteLine("x:0\\ny:1",x,y);
Console.WriteLine("Lambda:0",lambda.ToString());
/*输出:
结果对吗?我也不知道,我撸得这么辛苦,先让电脑休息一下,各位看官只要Ctrl+C,Ctrl+V就可以看到了.
*/
- OK,其它各种操作,只要依葫芦画瓢便大功可成! 先爽一下!
- 好,暂且爽到这里,欲看如何把括号弄成俄罗斯套娃,且看下回分解.
- 随手点个赞,让我也爽爽,可好!
关于Expression表达式树的拼接
最近在做项目中遇到一个问题,需求是这样的:
我要对已经存在的用户进行检索,可以根据用户的id 或者用户名其中的一部分字符来检索出来,这样就出现了三种情况 只有id,只有用户名中一部字符,或者全部都有.
我们用的MVC+EF5.0的框架,在BLL层进行查询的 时候需要构建lambda表达式来作为查询条件,但是,我们怎么来构建lambda来确定查询的条件呢?我们知道Express<Func<T,bool>>这样的一个参数可以是lambda表达式,但是这里的按条件拼接式不能使用委托链的形式的.当然还有一种解决办法,我把所有查询条件都写好,然后根据传过来的ID 或者用户名 来判断确定使用哪个..这样的判断逻辑混乱,代码冗长,我们就想找一个可以动态拼接查询条件的方法.
即按照id 或者用户名是否存在动态的来拼接查询条件.
首先我们需要知道,表达式构成部分,表达式是有两部分构成,Parameter和body,第一个是参数,第二个是表达式体,表达式体是二进制的位运算,也就是 比如(left&right)而left和right要返回的值必须是基本类型的值,也就是可以参与位运算的值.例如(a,b)=>()这个lambda表达式中,ab是参数,括号后面中是表达式体这里面返回的值只能是基本类型.我们要构建一个表达式树,主要就是构建这个表达式体,那么这个表达式体是一个什么样的类型呢 ?BinaryExpression类型,我们只需要构造这个类型,然后通过Expression.And(left,right)或者Expression.Or()这两个方法来构造即可. 这个两个方法返回值就是BinaryExpression的类型对象.然后我们在用Expression.Lambda<Func<T,bool>>(BinaryExpression,Parameter)这个方法将这个表达式树转化为lambda的表达式.这就是这个问题的 解决思路,来看看我们是怎么来实现的.
首先我们定义了一个表达式变量.
Expression<Func<UserInfo, bool>> where;
然后我们开始进行labmda的构造
接下来,我们来构造参数和必要条件,也是就lambda中的c=>()中的c
ParameterExpression param = Expression.Parameter(typeof(UserInfo), "c");//c=> //c=>c.IsDelete==false这里需要不被删除的条件 MemberExpression left1 = Expression.Property(param, typeof(UserInfo).GetProperty("IsDelete"));构建c.IsDelete ConstantExpression right1 = Expression.Constant(false);//构建一个常量 false BinaryExpression be = Expression.Equal(left1, right1);构建//c=>c.IsDelete==false 就是现在这个be了
下面 我们需要根据我们的条件 也就是id和用户名字符串来继续拼接这个表达式
首先我们来拼接c.UserId==sid
if (!string.IsNullOrEmpty(Request["sid"])) { //c.UserId==sid int sid = int.Parse(Request["sid"]); //根据参数的属性构造左表达式c.UserId MemberExpression left2 = Expression.Property(param, typeof(UserInfo).GetProperty("UserId")); //构造右表达式sid ConstantExpression right2 = Expression.Constant(sid); //进行合并:cUserId==sid BinaryExpression where2 = Expression.Equal(left2, right2); //将这个条件与上一个条件进行与合并:c.IsDelete==false && c.UserId==sid be = Expression.And(be, where2); }
现在我们来拼接第二个条件
前面我们已经说过,表达式体需要返回的是可以做二进制运算的类型,但是这是个值类型字符串,该怎么办呢?
在参考了MSDN中的Expression方法中,发现有这样的一个方法.Expression.Call().
然后看了示例这个 |
究竟是用来干嘛的?? |
我们可以用这个call’方法 ,来调用一个类型 中的一个方法,然后产生一个MethodCallExpression类型的返回值,这样,我们来调用string. Contains方法不就可以完成我们想要的表达式了么?
且看下面的 代码
//c.UserName.Contains(sname) if (!string.IsNullOrEmpty(Request["sname"])) { string sname = Request["sname"]; MemberExpression left3 = Expression.Property(param, typeof(UserInfo).GetProperty("UserName"));//这里构造c.UserName这个属性表达式. ConstantExpression right3 = Expression.Constant(sname);//这里构造sname这个常量表达式 MethodCallExpression where3 = Expression.Call(left3, typeof(string).GetMethod("Contains"), right3);这里我们用Call这个方法完成/c.UserName.Contains(sname)这个lambda这个表达式的实现. be = Expression.And(be, where3);//拼接刚才的be表达式, } where = Expression.Lambda<Func<UserInfo, bool>>(be, param);//生成最后需要的带参数的表达式树.
这样我们的表达式树拼接就完成了.
至于运行结果就不为大家贴图了,可以运行和lambda的结果一样.可以完成两个条件的查询.
下面,封装了这个表达式树的帮助类.大家可以参考.
public class WhereHelper<T> where T:class { private ParameterExpression param; private BinaryExpression filter; public WhereHelper() { param = Expression.Parameter(typeof (T), "c"); //1==1 Expression left = Expression.Constant(1); filter = Expression.Equal(left, left); } public Expression<Func<T, bool>> GetExpression() { return Expression.Lambda<Func<T, bool>>(filter,param); } public void Equal(string propertyName,object value) { Expression left = Expression.Property(param, typeof (T).GetProperty(propertyName)); Expression right = Expression.Constant(value, value.GetType()); Expression result = Expression.Equal(left, right); filter = Expression.And(filter, result); } public void Contains(string propertyName,string value) { Expression left = Expression.Property(param, typeof (T).GetProperty(propertyName)); Expression right = Expression.Constant(value, value.GetType()); Expression result = Expression.Call(left, typeof (string).GetMethod("Contains"), right); filter = Expression.And(filter, result); } }
当然,这个帮助类功能有限,如果有需要者,大家可以自己进行扩充.
本文所提到的技术,均为我师研究,因为他研究完之后就给我们讲解了原理和实现.我只是整理出来,给大家做
以上是关于基于Expression Lambda表达式树的通用复杂动态查询构建器——《构思篇二》已开源的主要内容,如果未能解决你的问题,请参考以下文章