基于Expression Lambda表达式树的通用复杂动态查询构建器——《构思篇二》已开源

Posted ls0001

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于Expression Lambda表达式树的通用复杂动态查询构建器——《构思篇二》已开源相关的知识,希望对你有一定的参考价值。

还在手撸查询代码吗?有没有想过全自动的,通用的,免写代码的动态查询?上一篇简略讲了如何把查询条件用代码描述出来,本篇接继来讲讲,如何根据前面设计好查询描述器构造出可执行的表达式。

接续[上篇之预告]

本篇来讲讲,如何根据前面设计的查询描述器构造出可执行的表达式。正如标题所示,实现手段将采用Expression Lambda技术。

先来看看主角System.Linq.Expressions.Expression 长什么样,都有些什么东西,能做什么。 先看看它的类图:

 

 

我们主要使用Expression这个类,它包含各种节点类型的 static工厂方法。特别是以下这些方法,对应了查询需要用到的逻辑、比较、数学,等各种运算操作。

逻辑运算  
AndAlso(Expression, Expression) 表示And逻辑运算
OrElse(Expression, Expression) 表示Or逻辑运算
比较运算  
LessThan(Expression, Expression) 表示小于:"<"
LessThanOrEqual(Expression, Expression) 表示大于等于:“<="
GreaterThan(Expression, Expression) 表示大于:”>“
GreaterThanOrEqual(Expression, Expression) 表示大等于:”>="
Equal(Expression, Expression) 表示等于:”=“
NotEqual(Expression, Expression) 表示不等于:”!="
IsTrue(Expression) 测试表达式结果是否为“True"
IsFalse(Expression) 测试表达式结果是否为“False"
Not(Expression) 表示”Not"
数学运算  
Add(Expression, Expression) 加法
Subtract(Expression, Expression) 减法
Multiply(Expression, Expression) 乘法
Divide(Expression, Expression) 除法
Modulo(Expression, Expression) 模除
Power(Expression, Expression) 幂运算
一些操作  
Parameter(Type) 包装一个类型为Type的参数
Call(Expression, MethodInfo, Expression, Expression) 包装一个方法调用
Bind(MemberInfo, Expression) 包装一个成员绑定,(属性或字段)
Quote(Expression) 包装一个括号
MakeUnary(ExpressionType, Expression, Type) 包装一个一元运算
MakeBinary(ExpressionType, Expression, Expression) 包装一个二元运算
TypeAs(Expression, Type) 包装一个显式引用或装箱转换,其中如果转换失败则提供“null”。
Coalesce(Expression, Expression, LambdaExpression) 包装表示给定转换函数的聚结操作。
变量、常量、字段、属性  
Default(Type) 包装一个指定类型的默认值
Constant(Object, Type) 包装一个常量
Assign(Expression, Expression) 赋值操作
Variable(Type, String) 包装一个变量定义
Property(Expression, MethodInfo) 包装一个属性
Field(Expression, FieldInfo) 包装一个字段
PropertyOrField(Expression, String) 包装一个指定名称的字段或属性
要的就是这结果  
Lambda(Expression, ParameterExpression[]) 包装一个委托,带有一个参数表达式数组。

以上仅列出了一部分,它涵盖了全部可能要使用到的函数,语句,欲详细了解可以直接乘坐火箭到微软官方网站查看。

这此函数或功能是有了,如何使用呢?来看个例子:将SQL:Table1.A > 5 and Table1.B=3,转成(Table1)=>Table1.A > 5 && Table1.B==3;

Expression的世界里一切都是Expression。因此首先要将Table1A5B3先包装成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);

        }

}

 

当然,这个帮助类功能有限,如果有需要者,大家可以自己进行扩充.

本文所提到的技术,均为我师研究,因为他研究完之后就给我们讲解了原理和实现.我只是整理出来,给大家做

原文  http://www.cnblogs.com/ruhuaxiao/p/3773596.html

以上是关于基于Expression Lambda表达式树的通用复杂动态查询构建器——《构思篇二》已开源的主要内容,如果未能解决你的问题,请参考以下文章

关于Expression表达式树的拼接

Lambda 到表达式树的转换

python's lambda expression

表达式树 Expression Trees

java Lambda expression

Expression.Call 在简单的 lambda 表达式中。可能吗?