找回密码
 立即注册
首页 业界区 业界 基于Expression Lambda表达式树的通用复杂动态查询构建 ...

基于Expression Lambda表达式树的通用复杂动态查询构建器——《原型篇一》[已开源]

嗣伐 前天 19:49
续接上编,本篇来讲讲俄罗斯套娃的设计与实现。
首先简单地完善一下前面提到的例子,代码如下:

  • 测试实体类
  1. //测试实体类
  2. public class Table
  3. {
  4.     public int A;
  5.     public int B;
  6. }
复制代码

  • 独立条件类
  1. //独立条件:
  2. public class Field
  3. {
  4.     public Logical Logical { get; set; }        //与其它条件之间的逻辑关系  
  5.     public Comparer Comparer { get; set; }      //条件比较符
  6.     public Type DataType { get; set; }          //数据类型
  7.     public string FieldName { get; set; }       //字段名称
  8.     public object QueryValue { get; set; }      //条件值
  9. }
复制代码

  • 条件组类
  1. //条件组:
  2. public class Block
  3. {
  4.     public Logical Logical { get; set; }      //与其它条件组或独立条件之间的逻辑关系  
  5.     public List<Field> Fields { get; set;}=new List<Field>();
  6.     public List<Block> Blocks { get;set; }=new List<Block>();
  7. }
复制代码

  • 枚举
  1. //逻辑
  2. public enum Logical
  3. {
  4.     And,
  5.     Or,
  6. }
  7. //比较
  8. public enum Comparer
  9. {
  10.     Equal,
  11.     GreatThan,
  12.     LessThan,
  13. }
复制代码
 
接下来,先构建查询条件描述器对象,由于例子代码比较简略,仅用于方便说明设计思路和方法,如果哪位看官直接拿来实用,请先备好洗澡水,以备从坑里爬出来净身之用。
代码简单就不作多说明了,就是添加一个独立条件,两个子组,子组里分别包含两个独立条件,以描述等效于
SQL=(Table1.A5 And Table1.B=3)) OR Table1.B>5的查询子句。
  1. Block CreaterDescriptor()
  2. {
  3.     //SQL: (Table1.A<3 OR Table1.A=3) Or  (Table1.A>5 And Table1.B=3)) OR Table1.B>5
  4.     var block = new Block() {Logical= Logical.Or};
  5.     block.Fields.AddRange(new[]{new Field(){ Comparer= Comparer.GreatThan, DataType= typeof(int), FieldName="B", Logical= Logical.Or, QueryValue=5}});
  6.     block.Blocks.AddRange(new [] {
  7.         new Block(){
  8.             Logical= Logical.Or,
  9.             Fields=new List<Field>(new Field[]{
  10.                 new Field(){ Comparer= Comparer.LessThan, DataType=typeof(int), FieldName="A", QueryValue= 3},
  11.                 new Field(){ Comparer= Comparer.Equal, DataType=typeof(int), FieldName="A", Logical= Logical.Or, QueryValue= 3}
  12.             }),
  13.         },
  14.         new Block(){
  15.             Logical= Logical.Or,
  16.             Fields=new List<Field>(new Field[]{
  17.                 new Field(){ Comparer= Comparer.GreatThan, DataType=typeof(int), FieldName="A", QueryValue= 5},
  18.                 new Field(){ Comparer= Comparer.Equal, DataType=typeof(int), FieldName="B", Logical= Logical.And,QueryValue= 3}
  19.             }),
  20.         },
  21.     });
  22.     return block;
  23. }
复制代码
至此,已经拿到查询条件描述对象,知道了需要以什么条件进行查询了,一下步就是如何其转换为查询委托。
先来个手动组装看看上篇的设想能不能行得通。
  1. Expression<Func<Table,bool>>  Manual()
  2. {
  3.     //SQL: (Table1.A<3 OR Table1.A=3) Or  (Table1.A>5 And Table1.B=3)) OR Table1.B>5
  4.     //老套路,先包装
  5.     var Table1 = new Table();
  6.     var p = Expression.Parameter(typeof(Table), "Table1");
  7.    
  8.     //将5、3这两个常量包装成ConstantExpression:
  9.     var num5 = Expression.Constant(5, typeof(int));
  10.     var num3 = Expression.Constant(3, typeof(int));
  11.    
  12.     //将两个属性包装成MemberExpression。
  13.     var a = Expression.PropertyOrField(p, "A");
  14.     var b = Expression.PropertyOrField(p, "B");
  15.    
  16.     //构造Table1.A<3:
  17.     var ltA3 = Expression.LessThan(a, num3);
  18.     //构造Table1.A=3:
  19.     var eqA3 = Expression.Equal(a, num3);
  20.     //构造Table1.A>5:
  21.     var gtA5 = Expression.GreaterThan(a, num5);
  22.     //构造Table1.A=5:
  23.     var eqB3 = Expression.Equal(b, num3);
  24.     //构造Table1.B>5:
  25.     var gtB5 = Expression.GreaterThan(b, num5);
  26.    
  27.     //构造Table1.A<3 OR Table1.A=3
  28.     var expLtA3orEqA3 = Expression.OrElse(ltA3, eqA3);
  29.     //构造Table1.A>5 && Table1.B=3
  30.     var expGtA5andEqB3 = Expression.AndAlso(gtA5, eqB3);
  31.     //构造(Table1.A<3 OR Table1.A=3) Or  (Table1.A>5 And Table1.B=3))
  32.     var expGtA5andEqA3_Or_expLtA3orEqA3=Expression.OrElse(expLtA3orEqA3,expGtA5andEqB3);
  33.     //(Table1.A<3 OR Table1.A=3) Or  (Table1.A>5 And Table1.B=3)) OR Table1.B>5
  34.     var result=Expression.OrElse(expGtA5andEqA3_Or_expLtA3orEqA3,gtB5);
  35.    
  36.     //结果要出来了
  37.     Expression<Func<Table,bool>> lambda=Expression.Lambda<Func<Table,bool>>(result,p);
  38.     return lambda;
  39. }
复制代码
手动组装好了,来测试验证一下:
  1. //测试方法
  2. void TestLambda(Expression<Func<Table,bool>> lambda)
  3. {
  4.     var list = new List<Table>
  5.         {
  6.             new Table{A=6,B=2},
  7.             new Table{A=5,B=6},
  8.             new Table{A=2,B=3}
  9.         }
  10.     ;
  11.     var my = list.Where(t => lambda.Compile()(t)).ToArray();
  12.     var linq = list.Where(t => ((t.A < 3 || t.A == 3) || (t.A > 5 && t.B == 3)) || t.B > 5).ToArray();
  13.     Debug.Assert(my.Length == linq.Length);
  14.     for (var i = 0; i < my.Length; i++)
  15.     {
  16.         Debug.Assert(my[i] == linq[i]);
  17.     }
  18.     Console.WriteLine("Test_Ok");
  19. }
复制代码
  1. //运行测试
  2. void Main()
  3. {
  4.     var lambda=Manual();
  5.     TestLambda(lambda );   
  6. }
复制代码
经过上机运行,结果正确,OK!
但是,问题来了,这手动组装并不简单,稍不留神就容易写错,如果真这么用,就是浪费表情了。那么有没有自动的,不需要人工干预的方法?必须有,否则本系列文章还有什么可写的呢?
具体看下面代码,就不太过啰嗦的解释了,简单说明一下思路,拿到一个多层嵌套的条件组之后,先对直接的独立条件进行LambdaExpression逐个组装,串联起来,再逐个组装子条件组也串联起来,最后把独立条件和子组串联起来,如果子组里还有子组,进行逐层递归:
  1. Expression<Func<T,bool>>  CreaterQueryExpression<T>(Block block)
  2. {
  3.     var param=Expression.Parameter(typeof(T),typeof(T).Name);
  4.     return Expression.Lambda<Func<T,bool>>(CreateBlockExpr(block),param);
  5.     //构建独立条件的串联
  6.     Expression CreateFieldExpr(List<Field> fields)
  7.     {
  8.         var lastExp=default(Expression);
  9.         foreach (var f in fields)
  10.         {
  11.             var member=Expression.PropertyOrField(param, f.FieldName);
  12.             var value=Expression.Constant(f.QueryValue);
  13.             var exp=f.Comparer switch
  14.             {
  15.                 Comparer.GreatThan=>Expression.GreaterThan(member,value),
  16.                 Comparer.LessThan=>Expression.LessThan(member,value),
  17.                 _=> Expression.Equal(member,value),
  18.             };
  19.             if (lastExp != default(Expression))
  20.             {
  21.                  exp= f.Logical switch
  22.                 {
  23.                     Logical.Or => Expression.OrElse(lastExp,exp),
  24.                     _=>Expression.AndAlso(lastExp,exp),
  25.                };
  26.             }
  27.             lastExp=exp;
  28.         }
  29.         return lastExp;
  30.     }
  31.     //构建子组并串联
  32.     Expression CreateBlockExpr(Block block)
  33.     {
  34.         var lastExp=default(Expression);
  35.         var exp=CreateFieldExpr(block.Fields);
  36.         foreach (var sub in block.Blocks)
  37.         {
  38.             var subExp = CreateBlockExpr(sub);
  39.             if (lastExp != default && subExp!=default)
  40.             {
  41.                 subExp = sub.Logical switch
  42.                 {
  43.                     Logical.Or => Expression.OrElse(lastExp, subExp),
  44.                     _ => Expression.AndAlso(lastExp, subExp),
  45.                 };
  46.             }
  47.             lastExp=subExp;
  48.         }
  49.         if (lastExp != default && exp!=default)
  50.         {
  51.             exp= block.Logical switch
  52.             {
  53.                 Logical.Or => Expression.OrElse(exp, lastExp),
  54.                 _ => Expression.AndAlso(exp, lastExp),
  55.             };
  56.         }
  57.         return exp;
  58.     }
  59.         
  60. }
复制代码
好,委托已经拿到。是否能正确实现查询意图呢?来测试一下:
  1. // 运行测试
  2. public static void Main(string[] args)
  3. {
  4.     var lambda=CreaterQueryExpression<Table>(CreaterDescriptor());
  5.     TestLambda(lambda);
  6. }
复制代码
经上机运行,结果OK!

  • 至此,初步的原型实现已经完成.
  • 接下来要做什么呢,是正式编码开战呢,还是思考一下,有什么应用场景,有什么优缺点,如果有缺点能不能克服?如果正式开战可能会遇到些什么问题或困难,要如何解决?
  • 敬请等下回分解,随手点个赞呗。
 
 

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册