C#表达式目录树系列之5 –动态创建查询表达式

概要

在项目开发中,根据用户的需求,一般来是,我们的查询表达式是固定的,新的查询需求都要通过代码的修改来实现。而对于不确定的查询条件,固定查询表达式的方式显然是行不通的。

针对固定查询表达式存在的问题,我们提出基于表达式目录树的解决方案,该解决方案能帮助我们实时自动构建任何需要的查询表达式,以应用对各种复杂的查询场景。

通过定义IQueryable的扩展方法,来实现查询表达式的实时构建,并且以保证构建的查询表达式同IQueryable已支持的EF查询或内存查询的兼容性,完整代码已经上传CSDN,需要的读者可以免费下载,如果下载失败请留言告诉我邮箱。

设计及关键代码实现

实现目标

基于既定需求,我们的实现目标如下:

  1. 支持的查询字段类型包括数字,字符串,日期,枚举和布尔:
    • 数字类型查询支持大于,小于,等于,大于等于,小于等于的比较操作
    • 字符串类型查询支持等于,包含,以指定字符开头或结尾的比较操作
    • 日期类型查询支持大于,小于,等于,大于等于,小于等于的比较操作
    • 枚举和布尔类型查询支持等于的比较操作
  2. 支持多个条件按照And或Or关系的组合查询,And为默认组合方式
  3. 支持数字,字符串,日期及和枚举类型的否定查询

设计实现

IQueryable的扩展方法定于如下:

  • public static IQueryable WhereEnhance (this IQueryable source, List conditions)
  • public static IQueryable WhereEnhance (this IQueryable source, ConditionTreeNode root)

针对不同复杂度的查询,我们定义了两个WhereEnhance的方法,两个方法接收的参数类型不同。

  • 对于方法1,将查询条件通过一个List传入。每个查询条件记录和下一个查询条件的逻辑关系And或者Or。因为每个节点只能记录和下一个节点的逻辑关系,对于复杂的嵌套逻辑关系无法支持,所以该方法只适用于查询条件的逻辑关系比较简单的查询情景, WhereCondition类的定义请参见附录。
  • 对于方法2,将查询条件通过一个二叉树的根节点传入。二叉树的非叶节点保存查询条件之间的逻辑关系And或者Or。叶节点保存具体的查询条件。这样,复杂的嵌套逻辑关系完全可以通过二叉树来表示,所以该方法适用于查询条件的逻辑关系比较复杂的情景。ConditionTreeNode 定义详见附录。

关键代码解释

WhereEnhance方法(接收List参数)

本文通过定义一个IQueryable的扩展方法WhereEnhance来实现上述查询表达式构建流程,该方法既可以用于内存数据的查询,也可以和EF配合在一起查询数据库中的数据。具体代码如下:

 public static IQueryable<T> WhereEnhance<T> (this IQueryable<T> source, List<WhereCondition> conditions) {
   
   
			ParameterExpression parameter = Expression.Parameter(typeof(T),"s");
			Expression conditionsExpression = CreateExpressions<T>(conditions, parameter);			
			LambdaExpression whereCondition = Expression.Lambda<Func<T,bool>>(conditionsExpression,new ParameterExpression[]{
   
   parameter});
			Console.WriteLine("Lambda Expression: " + whereCondition);
			MethodCallExpression whereCallExpression = Expression.Call(  
				typeof(Queryable),  
				"Where",  
				new Type[] {
   
    source.ElementType },  
				source.Expression,  
				whereCondition);  
			return source.Provider.CreateQuery<T>(whereCallExpression) as IQueryable<T>;    
		} 
private static Expression CreateExpressions<T>(List<WhereCondition> conditions, ParameterExpression parameter){
   
   		
			Expression conditionsExpression = Expression.And(Expression.Constant(true),Expression.Constant(true));
            ConditionRelation relation = ConditionRelation.And;
            for(var i = 0 ; i< conditions.Count; ++i){
   
   
                MemberExpression property = Expression.Property(parameter, conditions[i].PropertyName);		
				PropertyType propertyType = CheckFieldType(property);
			 	Expression whereCondition;
				if (propertyType == PropertyType.String){
   
   
					whereCondition = buildStringWhere<T>(conditions[i], parameter); 
				}else if (propertyType == PropertyType.Number
					 || propertyType == PropertyType.DateTime 
					 || propertyType == PropertyType.Enum
					 || propertyType == PropertyType.Bool){
   
   
					whereCondition = buildNumberOrDateTimeWhere<T>(conditions[i], parameter);
				}else{
   
   
					throw new ArgumentException($"We cannot support the property type {property.Type} in {typeof(T)}. ");
				}       
                if (i==0){
   
   
                    conditionsExpression = whereCondition;
                    relation = conditions[i].NextRelation;
                }else{
   
   
                    conditionsExpression = Combine<T>(
					    conditionsExpression,
						whereCondition,
                        relation
				    );
                    relation = conditions[i].NextRelation;
                }
            }
			return conditionsExpression;
		} 
  1. 通过在该方法中定义泛型参数,实现在不同的WepAPI中,所有业务相关类型的查询支持。在实际应用中,建议增加泛型约束。
  2. 构建Lambda表达式左侧的变量s,类型为T。
  3. 调用方法CreateExpressions创建查询表达式,其中传入的查询条件排列顺序,即为这些条件生成对应查询表达式后,表达式进行And或Or组合时候的顺序。
  4. 遍历所有查询条件,生成具体的查询表达式。
    • 调用CheckFieldType方法,检查属性类型,该方法请见附录。
    • 字符串类型的属性,所有的比较全部通过字符串类的实例方法实现,所以定义函数buildStringWhere来实现字符串查询表达式的构建。
    • 数字,日期,枚举都支持通过操作符 < > 或=的比较,布尔类型也支持=操作符。因此将这些类型查询表达式构建分为一类,通过buildNumberOrDateTimeWhere方法构建查询表达式。
    • 将生成的查询表达式通过Combine方法组合在一起。Combine方法支持And和Or两种组合方式,默认支持And方式。Combine方法定义详见附录。
  5. 将构建好的表达式和Lambda表达式组装到一起,构建出Lambda表达式。
  6. 调用Where方法,执行Lambda表达式。

WhereEnhance方法(接收二叉树根节点参数)

public static IQueryable<T> WhereEnhance<T> (this IQueryable<T> source, ConditionTreeNode root) {
   
   
			ParameterExpression parameter = Expression.Parameter(typeof(T),"s");
			Expression conditionsExpression = CreateExpressions<T>(root, parameter);	
			LambdaExpression whereCondition = Expression.Lambda<Func<T,bool>>(conditionsExpression,new ParameterExpression[]{
   
   parameter});
			Console.WriteLine("Lambda Expression: " + whereCondition);
			MethodCallExpression whereCallExpression = Expression.Call(  
				typeof(Queryable),  
				"Where",  
				new Type[] {
   
    source.ElementType },  
				source.Expression,  
				whereCondition
				);  
			return source.Provider.CreateQuery<T>(whereCallExpression) as IQueryable<T>;    
		}
private static Expression CreateExpressions<T>(ConditionTreeNode node, ParameterExpression parameter){
   
   	
			if (node.Relation != ConditionRelation.None){
   
   
				return Combine<T>(
					CreateExpressions<T>(node.LeftNode, parameter), 
					CreateExpressions<T>(node.RightNode, parameter),
					node.Relation
					);
			}
			else{
   
   
				MemberExpression property = Expression.Property(parameter, node.NodeValue.PropertyName);		
				PropertyType propertyType = CheckFieldType(property);
				Expression whereCondition;
				if (propertyType == PropertyType.String){
   
   
					whereCondition = buildStringWhere<T>(node.NodeValue, parameter); 
				}else if (propertyType == PropertyType.Number || propertyType == PropertyType.DateTime || propertyType == PropertyType.Enum){
   
   
					whereCondition = buildNumberOrDateTimeWhere<T>(node.NodeValue, parameter);
				}else{
   
   
					throw new ArgumentException($"We cannot support the property type {property.Type} in {typeof(T)}. ");
				}
				return whereCondition;
			}	
		}

  1. 通过在该方法中定义泛型参数,实现在不同的WepAPI中,所有业务相关类型的查询支持。在实际应用中,建议增加泛型约束。
  2. 构建Lambda表达式左侧的变量s,类型为T。
  3. 调用方法CreateExpressions创建查询表达式,传入的查询条件是一棵二叉树根的节点。
    • 如果当前节点是非叶节点:
      • 递归构建当前节点的左子树节点。
      • 递归构建当前节点的右子树节点。
      • 将生成的查询表达式通过Combine方法组合在一起。Combine方法支持And和Or两种组合方式,默认支持And方式。Combine方法定义详见附录。
      • 返回构建好的查询表达式。
    • 如果当前节点是叶节点:
      • 调用CheckFieldType方法,检查属性类型,该方法请见附录。
      • 字符串类型的属性,所有的比较全部通过字符串类的实例方法实现,所以定义函数buildStringWhere来实现字符串查询表达式的构建。
      • 数字,日期,枚举都支持通过操作符 < > 或=的比较,布尔类型也支持=操作符。因此将这些类型查询表达式构建分为一类,通过buildNumberOrDateTimeWhere方法构建查询表达式。
      • 返回构建好的查询表达式。
  4. 将构建好的表达式和Lambda表达式组装到一起,构建出Lambda表达式。
  5. 调用Where方法,执行Lambda表达式。

buildStringWhere方法

buildStringWhere方法用于构建字符串类型的查询表达式。构建目标:

s => s.Property.Method(keyword),其中:

  • s的类型是T,在运行时会传入具体的类型。
  • Property是类中的某一个属性,类型为字符串类型。
  • Method是具体字符串比较方法,支持Equals,Contains,StartsWith和EndsWith,4个字符串比较方法。
  • keyword是查询关键字。
private static Expression buildStringWhere<T>(WhereCondition condition, ParameterExpression parameter){
   
   
			Dictionary<string,string> methodsMap = new Dictionary<string,string>(){
   
   
				{
   
    EQ, "Equals"},
				{
   
   "contain", "Contains"},
				{
   
   "beginwith", "StartsWith"},
				{
   
   "endwith", "EndsWith"},
			};
			MemberExpression property = Expression.Property(parameter, condition.PropertyName);
			ConstantExpression rightValue = GetConstantExpression(condition.PropertyValue, property);
			ConstantExpression optionValue = Expression.Constant(StringComparison.CurrentCultureIgnoreCase);
			Expression methodCall = Expression.Call(
					property,
					typeof(string).GetMethod(methodsMap[condition.Action], 
					new Type[]{
   
    
						typeof(string),
						//typeof(StringComparison)
						}), // method
					new Expression[] // Work method's parameter
					{
   
   
						rightValue,
					//	optionValue
					}
				);

			if (condition.Reversed){
   
   
				methodCall = Expression.Not(methodCall);
			}
			return methodCall;
		}
  • 根据查询条件查询条件中的关键字,调用GetConstantNumberExpression方法,构建关键字的常量表达式。关键字类型与对应的类中属性类型一致。该方法请见附录。
  • 根据查询条件,调用具体的字符串比较方法。对于忽略大小写的查询,只能支持内存查询。如果需要在EFCore的查询中使用,需要在数据库中进行配置,因此暂时注释掉了对应代码。
  • 如果是否定比较,将原有查询表达式,传化为否定查询表达式。
  • 返回构建好的字符串查询表达式。

buildNumberOrDateTimeWhere方法

buildNumberOrDateTimeWhere方法用于构建数字,枚举和日期类型的查询表达式。构建目标:
s=> s.Property [>|<|=|<=|>=] keywork 其中:

  • s的类型是T,在运行时会传入具体的类型。
  • Property是类中的某一个属性,类型数字,枚举或日期类型。其中数字类型支持decimal,int, long,short,byte类型。
  • 比较操作符包含大于,小于,等于,小于等于,大于等于中的一个。
  • keyword是查询关键字。
private static Expression buildNumberOrDateTimeWhere<T>(WhereCondition condition, ParameterExpression parameter){
   
   	
			MemberExpression propert = Expression.Property(parameter, condition.PropertyName);
			ConstantExpression rightValue = GetConstantExpression(condition.PropertyValue, property);
			Expression whereCondition;
			switch(condition.Action){
   
   
				case GT:
					whereCondition = Expression.GreaterThan(property, rightValue);
					break;
				case GE:
					whereCondition = Expression.GreaterThanOrEqual(property, rightValue);
					break;
				case LT:
					whereCondition = Expression.LessThan(property, rightValue);
					break;
				case LE:
					whereCondition = Expression.LessThanOrEqual(property, rightValue);
					break;
				default:
					whereCondition = Expression.Equal(property, rightValue);
					break;
			}			
			if (condition.Reversed){
   
   
				whereCondition = Expression.Not(whereCondition);
			}
			return whereCondition; 
		} 
  • 根据查询条件查询条件中的关键字,调用GetConstantExpression方法,构建关键字的常量表达式。关键字类型与对应的类中属性类型一致。该方法请见附录。
  • 根据查询条件,选择具体的比较操作符。
  • 如果是否定比较,将原有查询表达式传化为否定查询表达式。
  • 返回构建好的查询表达式。

验证及测试

本次开发的WhereEnhance方法,作为IQueryable的扩展方法,可以支持内存查询和配合EFCore,进行数据库的查询。本文以查询银行分行信息作为查询数据,进行查询。

由于内存数据查询和数据库查询使用的数据和测试结果完全相同,故本文只提供数据库查询的测试用例和结果。如果需要内存查询的相关数据,请参看完整代码。测试数据详见附录。

Branch类支持EFCore的完整代码

  [Table ("t_branch")]
     public class Branch{
   
   
        [Key,DatabaseGenerated(DatabaseGeneratedOption.Identity), Column(Order=0)]
        public int Id {
   
   get;set;}
        [Required, Column(Order=1)]
        public string BranchName {
   
   get;set;}
        [Required, Column(Order=2)]
        public string BranchManager {
   
   get;set;}
        [Required, Column(Order=3)]
        public int BranchCode {
   
   get;set;}
        [Column (TypeName = "datetime", Order=4), Required]
        public DateTime BuildDate {
   
   get;set;}
        [Required, Column(Order=5)]
        public BranchStatus Status {
   
   get;set;} = BranchStatus.Open;
        [Required, Column(Order=6)]
        public bool HasATM {
   
   get;set;} =  true;
        [Timestamp, Column(Order=7)]
        public byte[] RowVersion {
   
    get; set; }
    }

数据库建表语句和初始化数据请参看附录。

测试用例

查询分行代码小于4的分行信息

 using(var context = new ExpressionTreeContext()){
   
   
            WhereCondition condition = new WhereCondition(){
   
   
                PropertyName = "BranchCode",
                PropertyValue = "4",
                Action = ActionType.lt.ToString(),
                Reversed = false
            };
            List<WhereCondition> conditions = new List<WhereCondition>(){
   
   
		        condition,
		    };
            var branches = await context.Branches
                .WhereEnhance(conditions)
                .AsNoTracking ()
                .ToListAsync ();
                foreach(var branch in branches){
   
   
                    System.Console.WriteLine(branch.BranchName);
                }
        }  

执行结果:
在这里插入图片描述

查询分行名称以“分行2”结尾的分行信息

 using(var context = new ExpressionTreeContext()){
   
   
            WhereCondition condition = new WhereCondition(){
   
   
                PropertyName = "BranchName",
                PropertyValue = "分行2",
                Action = ActionType.endwith.ToString(),
                Reversed = false
		    };
            List<WhereCondition> conditions = new List<WhereCondition>(){
   
   
		        condition,
		    };
            var branches = await context.Branches
                .WhereEnhance(conditions)
                .AsNoTracking ()
                .ToListAsync ();
                foreach(var branch in branches){
   
   
                    System.Console.WriteLine(branch.BranchName);
                }
        }

执行结果:

在这里插入图片描述

查询成立日期晚于2016-10-04的分行信息

using(var context = new ExpressionTreeContext()){
   
   
            WhereCondition condition = new WhereCondition(){
   
   
                PropertyName = "BuildDate",
			    PropertyValue = "2016-10-04",
			    Action = ActionType.gt.ToString(),        
			    Reversed = false
		    };
            List<WhereCondition> conditions = new List<WhereCondition>(){
   
   
		        condition,
		    };
            var branches = await context.Branches
                .WhereEnhance(conditions)
                .AsNoTracking ()
                .ToListAsync ();
                foreach(var branch in branches){
   
   
                    System.Console.WriteLine(branch.BranchName);
                }
        }   

执行结果:
在这里插入图片描述

查询状态为Open的分行信息

using(var context = new ExpressionTreeContext()){
   
   
            WhereCondition condition = new WhereCondition(){
   
   
                PropertyName = "Status",
                PropertyValue = "1",
                Action = ActionType.eq.ToString(),
                Reversed = false
		    };
            List<WhereCondition> conditions = new List<WhereCondition>(){
   
   
		        condition,
		    };
            var branches = await context.Branches
                .WhereEnhance(conditions)
                .AsNoTracking ()
                .ToListAsync ();
                foreach(var branch in branches){
   
   
                    System.Console.WriteLine(branch.BranchName);
                }
        }    

执行结果:
在这里插入图片描述

查询没有ATM机的分行信息

using(var context = new ExpressionTreeContext()){
   
   
            WhereCondition condition = new WhereCondition(){
   
   
                PropertyName = "HasATM",
                PropertyValue = "false",
                Action = ActionType.eq.ToString(),
                Reversed = false
		    };
            List<WhereCondition> conditions = new List<WhereCondition>(){
   
   
		        condition,
		    };
            var branches = await context.Branches
                .WhereEnhance(conditions)
                .AsNoTracking ()
                .ToListAsync ();
                foreach(var branch in branches){
   
   
                    System.Console.WriteLine(branch.BranchName);
                }
        }    

执行结果:
在这里插入图片描述

查询类型不是Open的分行信息

  using(var context = new ExpressionTreeContext()){
   
   
            WhereCondition condition = new WhereCondition(){
   
   
                PropertyName = "Status",
                PropertyValue = "2",
                Action = ActionType.eq.ToString(),
                Reversed = true
		    };
            List<WhereCondition> conditions = new List<WhereCondition>(){
   
   
		        condition,
		    };
            var branches = await context.Branches
                .WhereEnhance(conditions)
                .AsNoTracking ()
                .ToListAsync ();
            foreach(var branch in branches){
   
   
                System.Console.WriteLine(branch.BranchName);
            }
        }    

执行结果:
在这里插入图片描述

查询成立日期不晚于2016-10-04的分行信息

using(var context = new ExpressionTreeContext()){
   
   
            WhereCondition condition = new WhereCondition(){
   
   
                PropertyName = "BuildDate",
			    PropertyValue = "2016-10-04",
			    Action = ActionType.gt.ToString(),        
			    Reversed = true
		    };
            List<WhereCondition> conditions = new List<WhereCondition>(){
   
   
		        condition,
		    };
            var branches = await context.Branches
                .WhereEnhance(conditions)
                .AsNoTracking ()
                .ToListAsync ();
            foreach(var branch in branches){
   
   
                System.Console.WriteLine(branch.BranchName);
            }
        }   

执行结果:
在这里插入图片描述

查询成立日期在2016-10-02到2016-10-06之间,不包含2016-10-02的分行信息

using(var context = new ExpressionTreeContext()){
   
   
            WhereCondition conditionStartDate = new WhereCondition(){
   
   
                PropertyName = "BuildDate",
                PropertyValue = "2016-10-02",
                Action = ActionType.gt.ToString(),
                Reversed = false
		    };
            WhereCondition conditionEndDate = new WhereCondition(){
   
   
                PropertyName = "BuildDate",
                PropertyValue = "2016-10-06",
                Action = ActionType.le.ToString(),
                Reversed = false
		    };
            List<WhereCondition> conditions = new List<WhereCondition>(){
   
   
		        conditionStartDate,
                conditionEndDate,
		    };
            var branches = await context.Branches
                .WhereEnhance(conditions)
                .AsNoTracking ()
                .ToListAsync ();
            foreach(var branch in branches){
   
   
                System.Console.WriteLine(branch.BranchName);
            }
        }   

执行结果:
在这里插入图片描述

查询成立日期在2016-10-02到2016-10-07之间,不包含2016-10-02并且分行类型不是Open的分行信息

 using(var context = new ExpressionTreeContext()){
   
   
            WhereCondition condition = new WhereCondition(){
   
   
                PropertyName = "Status",
                PropertyValue = "1",
                Action = ActionType.eq.ToString(),
                Reversed = true
		    };
            WhereCondition conditionStartDate = new WhereCondition(){
   
   
                PropertyName = "BuildDate",
                PropertyValue = "2016-10-02",
                Action = ActionType.gt.ToString(),
                Reversed = false
		    };
            WhereCondition conditionEndDate = new WhereCondition(){
   
   
                PropertyName = "BuildDate",
                PropertyValue = "2016-10-07",
                Action = ActionType.le.ToString(),
                Reversed = false
		    };
            List<WhereCondition> conditions = new List<WhereCondition>(){
   
                 
		        conditionStartDate,
                conditionEndDate,
                condition,
		    };
            var branches = await context.Branches
                .WhereEnhance(conditions)
                .AsNoTracking ()
                .ToListAsync ();
            foreach(var branch in branches){
   
   
                System.Console.WriteLine(branch.BranchName);
            }
        }   

执行结果:
在这里插入图片描述

查询成立日期早于2016-10-02或者晚于2016-10-09,并且分行经理是Tom的分行信息

using(var context = new ExpressionTreeContext()){
   
   
            WhereCondition condition = new WhereCondition(){
   
   
                PropertyName = "BranchManager",
                PropertyValue = "Tom",
                Action = ActionType.eq.ToString(),
                Reversed = false
		    };
            WhereCondition conditionStartDate = new WhereCondition(){
   
   
                PropertyName = "BuildDate",
                PropertyValue = "2016-10-02",
                Action = ActionType.lt.ToString(),
                NextRelation = ConditionRelation.Or,
                Reversed = false
		    };
            WhereCondition conditionEndDate = new WhereCondition(){
   
   
                PropertyName = "BuildDate",
                PropertyValue = "2016-10-04",
                Action = ActionType.gt.ToString(),
                Reversed = false
		    };
            List<WhereCondition> conditions = new List<WhereCondition>(){
   
                 
                conditionStartDate,
                conditionEndDate,
                condition,
		    };
            var branches = await context.Branches
                .WhereEnhance(conditions)
                .AsNoTracking ()
                .ToListAsync ();
            foreach(var branch in branches){
   
   
                System.Console.WriteLine(branch.BranchName);
            }
        }     

执行结果:
在这里插入图片描述

如果我们将执行顺序改为如下:

List<WhereCondition> conditions = new List<WhereCondition>(){
   
                 
		       condition,
               conditionStartDate,
               conditionEndDate,
		    };

执行结果如下:
在这里插入图片描述

根据测试结果可以看到,如果改变条件的顺序,无论时生成的查询表达式,还是EFCore生成的SQL都不是我们预期的。由于当前版本不支持括号,请将Or操作尽量放到查询条件的最前面。

复杂条件查询

查询条件的Lambda表达式构建目标:

t => (([t].[BranchCode] > 1) AND ([t].[BranchCode] < 3)) OR ((([t].[BranchCode] < 9) AND ([t].[BranchCode] > 5))
AND ([t].[BranchManager] = N’Tom’))

using(var context = new ExpressionTreeContext()){
   
   
            ConditionTreeNode root = new ConditionTreeNode();
            root.Relation = ConditionRelation.Or;
            ConditionTreeNode p11 = new ConditionTreeNode();
            p11.Relation = ConditionRelation.And;
            root.LeftNode = p11; 
            ConditionTreeNode p12 = new ConditionTreeNode();
            p12.Relation = ConditionRelation.And;
            root.RightNode = p12;
            
            
            ConditionTreeNode p21 = new ConditionTreeNode();
            p11.LeftNode = p21;
            p21.NodeValue = new WhereCondition(){
   
   
                PropertyName = "BranchCode",
                Action = "gt",
                PropertyValue = "1"
            };
            ConditionTreeNode p22 = new ConditionTreeNode();
            p11.RightNode = p22;
            p22.NodeValue = new WhereCondition(){
   
   
                PropertyName = "BranchCode",
                Action = "lt",
                PropertyValue = "3"
            };
            ConditionTreeNode p23 = new ConditionTreeNode();
            p23.Relation = ConditionRelation.And;

            ConditionTreeNode p24 = new ConditionTreeNode();
            p24.NodeValue = new WhereCondition(){
   
   
                PropertyName = "BranchManager",
                Action = "eq",
                PropertyValue = "Tom",
                Reversed = false
            };
            p12.LeftNode = p23;
            p12.RightNode = p24; 
            ConditionTreeNode p31 = new ConditionTreeNode();
            p31.NodeValue = new WhereCondition(){
   
   
                PropertyName = "BranchCode",
                Action = "lt",
                PropertyValue = "9"
            };
            ConditionTreeNode p32 = new ConditionTreeNode();
            p32.NodeValue = new WhereCondition(){
   
   
                PropertyName = "BranchCode",
                Action = "gt",
                PropertyValue = "5"
            };
            p23.LeftNode = p31;
            p23.RightNode = p32;
            string json = JsonConvert.SerializeObject(root);
            root = JsonConvert.DeserializeObject<ConditionTreeNode>(json); 

            var branches = await context.Branches
                .WhereEnhance(root)
                .AsNoTracking ()
                .ToListAsync ();
            foreach(var branch in branches){
   
   
                System.Console.WriteLine(branch.BranchName);
            }
        }    

执行结果如下:
在这里插入图片描述

表达式目录树系列全部文章

附录

查询条件相关的类

public class WhereCondition{
   
   
        public string PropertyName {
   
   get;set;}
        public string Action {
   
   get;set;}
        public bool Reversed  {
   
   get;set;} = false;
        public string PropertyValue {
   
   get;set;}
        public ConditionRelation NextRelation {
   
   get;set;} = ConditionRelation.And;
    }
    public enum ConditionRelation{
   
   
        And= 1,
        Or,
        None
    }
    public class ConditionTreeNode{
   
   
        public ConditionTreeNode LeftNode {
   
   get;set;}
        public ConditionTreeNode RightNode {
   
   get;set;}
        public ConditionRelation Relation {
   
   get;set;} = ConditionRelation.None;
        public WhereCondition NodeValue {
   
   get;set;}
    }

WhereCondition类具体内容包括:

  • 查询字段名称
  • 查询条件,例如eq,gt等,具体请参见代码。
  • 查询关键字
  • Reversed字段表示是否是否定查询,即不等于,不大于,不包含等,默认是false
  • ConditionRelation是当前查询条件和下一个条件的关系,默认是And,可以设置为Or

ConditionTreeNode类具体内容包括:

  • 二叉树的左右节点
  • 如果是非叶节点,则Relation记录其左右节点的逻辑关系And或者Or。如果是叶节点,Relation为None。
  • 如果是非叶节点,NodeValue为空。如果是叶节点,NodeValue是WhereCondition类的对象。

分行类代码

public class Branch {
   
   
	public string BranchName {
   
   get;set;}
	public int BranchCode {
   
   get;set;}
	public DateTime BuildDate {
   
   get;set;}
	public BranchStatus Status {
   
   get;set;} = BranchStatus.Open;
	public string BranchManager {
   
   get;set;}
}
public enum BranchType {
   
   
	Open = 1,
	Closed,
	Maintance
}

判断属性类型的代码

public enum PropertyType {
   
   
	String = 1,
	Number,
	DateTime,
	Enum,
	Bool,
	Unsupported
}
private static PropertyType CheckFieldType(MemberExpression member){
   
   
			string typeName = member.Type.ToString();
			string baseTypeName = member.Type.BaseType.ToString();
			string[] supportedNumbertypes = new string []{
   
   DECIMAL, BYTE, SHORT , INT, LONG};	
			if (baseTypeName == ENUM){
   
   
				return PropertyType.Enum;
			}		
			if (typeName == STRING){
   
   
				return PropertyType.String;
			}else if (supportedNumbertypes.Contains(typeName)){
   
   
				return PropertyType.Number;
			}else if (typeName == DATETIME){
   
   
				return PropertyType.DateTime;
			} else if (typeName == BOOL){
   
   
				return PropertyType.Bool;
			}
			return PropertyType.Unsupported;
		}

创建常量表达式的代码

private static ConstantExpression GetConstantExpression(string val, MemberExpression member){
   
   
			ConstantExpression constExpression;
			string typeName = member.Type.ToString();
			string baseTypeName = member.Type.BaseType.ToString();
			switch(typeName){
   
   
				case STRING:
					constExpression = Expression.Constant(val, member.Type);
					break;
				case DECIMAL:
					constExpression = Expression.Constant(Convert.ToDecimal(val), member.Type);
					break;
				case SHORT:
					constExpression = Expression.Constant(Convert.ToInt16(val), member.Type);
					break;
				case INT:
					constExpression = Expression.Constant(Convert.ToInt32(val), member.Type);
					break;
				case LONG:
					constExpression = Expression.Constant(Convert.ToInt64(val), member.Type);
					break;
				case DATETIME:
					constExpression = Expression.Constant(DateTime.Parse(val), member.Type);
					break;
				case BYTE:
                    constExpression = Expression.Constant(Convert.ToByte(val), member.Type);
                    break;
				case BOOL:
                    constExpression = Expression.Constant(Convert.ToBoolean(val), member.Type);
                    break;                    
				default:
					if (baseTypeName == ENUM){
   
   
						int data = Int32.Parse(val.ToString());
                		String name = Enum.GetName(member.Type, data);
						constExpression = Expression.Constant(Enum.Parse(member.Type, name, false), member.Type);
					}else{
   
   
						constExpression = Expression.Constant(Convert.ToInt64(val), member.Type);
					}
					break;
			}
			return constExpression;
		}		

    }

注意枚举类型需要通过基类来判断。

查询表达式组合代码

private static Expression Combine<T>
        (
            Expression first, 
            Expression second,
            ConditionRelation relation = ConditionRelation.And
            )
        {
   
   
            if ( relation == ConditionRelation.And){
   
   
                return Expression.AndAlso(first, second);
            }else{
   
   
                return Expression.OrElse(first, second); 
            }
        }		

测试数据

数据库建表语句:

USE [Bank]
GO

/****** Object:  Table [dbo].[t_branch]    Script Date: 2021/2/1 16:38:49 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[t_branch](
	[Id] [int] IDENTITY(1,1) NOT NULL,
	[BranchName] [nvarchar](max) NOT NULL,
	[BranchCode] [int] NOT NULL,
	[BuildDate] [datetime] NOT NULL,
	[RowVersion] [timestamp] NULL,
	[BranchManager] [nvarchar](max) NOT NULL,
	[HasATM] [bit] NOT NULL,
	[Status] [int] NOT NULL,
 CONSTRAINT [PK_t_branch] PRIMARY KEY CLUSTERED 
(
	[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

GO

ALTER TABLE [dbo].[t_branch] ADD  DEFAULT (N'') FOR [BranchManager]
GO

ALTER TABLE [dbo].[t_branch] ADD  DEFAULT (CONVERT([bit],(0))) FOR [HasATM]
GO

ALTER TABLE [dbo].[t_branch] ADD  DEFAULT ((0)) FOR [Status]
GO

数据初始化语句:

 insert into [dbo].[t_branch] ([BranchName], [BranchCode], [BuildDate],[BranchManager], [HasATM], [Status] ) values
  (N'天津分行1',1, '2016-10-01', N'Tom',1,1),
  (N'天津分行2',2, '2016-10-02', N'Tom',1,1),
  (N'天津分行3',3, '2016-10-03', N'Tom',1,1),
  (N'天津分行4',4, '2016-10-04', N'Tom',1,1),
  (N'天津分行5',5, '2016-10-05', N'Tom',1,1),
  (N'天津分行6',6, '2016-10-06', N'Tom',0,1),
  (N'天津分行7',7, '2016-10-07', N'Jack',1,2),
  (N'天津分行8',8, '2016-10-08', N'Jack',1,2),
  (N'天津分行9',9, '2016-10-09', N'Mary',1,3)

展望

本次开发的WhereEnhance方法,让我们可以借助表达式目录树相关的类和方法,根据需要动态构建查询表达式,便于业务系统的扩展。但还存在一些不足,未来可以加强的功能包括:

  1. 当前对于查询条件的组合顺序上,仅支持按照标号,顺序组合,未来可以支持通过增加括号,按照括号的优先级进行组合。
  2. 现在的查询表达式的构建支持数字,字符串,枚举和日期4种常用数据类型,未来会支持更多类型,例如各种类型对应的可空类型等。

表达式目录树系列全部文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章