Expression表达式树动态查询

2017-05-04 20:37
在进行数据列表的查询中,我们通常会使用两种方式进行查询:
  1. linq查询
  2. 数据库sql语句查询

这样固然可以实现查询,本人之前也都是这么做的,因为查询的条件很少。使用linq,可以将所有的查询条件的属性传到后台,再根据该属性是否有值,使用where进行查询;使用存储过程,也需要将所有查询条件的属性传到后台,

再根据该属性是否有值进行sql语句的拼接。这样做在查询条件很少的时候固然没啥影响,但是有一天做查询列表的时候,本人碰到了一个查询条件高达接近10个的情况,这样再使用上述的方法固然也可以实现,但是可能会使用多个

条件去判断,然后使用多个where查询,如果在数据库使用sql语句拼接,可能会产生很长的sql拼接查询的代码,在调试的时候还要用print去打印才能看到完整的查询条件,所以我就想,能不能写一种方法可以动态产生查询的条件

,不管列表的查询条件有十个还是几十个,我只需要将查询的属性作为一个对象传到后台,再将这个查询属性的对象丢给这个方法,让它动态产生查询的表达式,然后使用linq中的where去查询呢?于是我便踏上了探索Expression表

达式树的路途!下面我写出自己的代码,希望和大家能一起交流学习!

 

经过查询资料和研究,终于写出了以下这套代码来达到目的:

1.定义一个查询的特性类,_displayName用于指明查询条件的类的各个属性用于和数据源列表元素对象的各个属性比较的属性名称(注意:_displayName的值必须和数据源中各个属性名称相同),_operation用于指明比较的操作

类型,类的代码如下:

 1 public class SearchAttribute : Attribute
 2 {
 3         /// <summary>
 4         /// 属性名称,用于对比查询
 5         /// </summary>
 6         private string _displayName;
 7         /// <summary>
 8         /// 操作类型
 9         /// </summary>
10         private OperationType _operation;
11 
12         public string DisplayName
13         {
14             get { return _displayName; }
15         }
16         public OperationType Operation
17         {
18             get { return _operation; }
19         }
20 
21         public SearchAttribute(string displayName, OperationType operation)
22         {
23             _displayName = displayName;
24             _operation = operation;
25         }
26 
27         /// <summary>
28         /// 不是查询的条件时调用此构造函数  参数值=OperationType.None
29         /// </summary>
30         /// <param name="operation"></param>
31         public SearchAttribute(OperationType operation)
32         {
33             _operation = operation;
34         }
35 }

2.定义一个比较的操作类型的枚举类型
 1     /// <summary>
 2     /// 查询操作类型
 3     /// </summary>
 4     public enum OperationType
 5     {
 6         /// <summary>
 7         /// 不进行查询
 8         /// </summary>
 9         None,
10         /// <summary>
11         /// 比较该查询属性的值是否与元数据数据的值相等 即sql中=
12         /// </summary>
13         Equal,
14         /// <summary>
15         /// 比较元数据数据的值是否包含该查询属性的值  即sql中like
16         /// </summary>
17         Like,
18         /// <summary>
19         /// 大于
20         /// </summary>
21         GreaterThan,
22         /// <summary>
23         /// 小于
24         /// </summary>
25         LessThan,
26         /// <summary>
27         /// >=
28         /// </summary>
29         GreaterThanOrEqual,
30         /// <summary>
31         /// <=
32         /// </summary>
33         LessThanOrEqual
34     }

3.核心代码来了,哈哈
 1 public static class Query
 2     {
 3         public static IQueryable<TSource> Search<TSource,T>(this IQueryable<TSource> queryList, T searchOptions)
 4         {
 5             return queryList.Where(Search<TSource,T>(searchOptions));
 6         }
 7 
 8         private static Expression<Func<TSource, bool>> Search<TSource,T>(T searchOptionEntity)
 9         {
10             var dataSouceType = typeof(TSource);  //数据源列表元素对象的类型
11             var dataSource = new
12             {
13                 Type = dataSouceType,  //数据源列表元素对象的类型
14                 Properties = dataSouceType.GetProperties(),  //数据源列表元素对象的属性集合
15             };            
16 
17             //List<string> sourcePropertyName = sourceProperties.Select(p => p.Name).ToList();
18 
19             PropertyInfo[] searchProperties = searchOptionEntity.GetType().GetProperties();  //查询选择器对象的属性集合
20 
21             var pe = Expression.Parameter(dataSource.Type, "p"); //创建一个 ParameterExpression 节点,该节点可用于标识表达式树中的参数或变量
22            var expression = Expression.Equal(Expression.Constant(true), Expression.Constant(true));
23 
24             //遍历查询选择器对象的属性集合
25             foreach (var property in searchProperties)
26             {
27                 var propertySearchAttribute = property.GetCustomAttributes(true)[0] as SearchAttribute;  //获取查询选择器属性的自定义特性对象
28                 var propertySearchVlaue = property.GetValue(searchOptionEntity, null);  //获取查询选择器属性的值
29                 var propertySearchAttributeName = propertySearchAttribute.DisplayName;  //获取查询选择器属性的自定义特性对象的对比查询的字段名称
30 
31                 //查询选择器中的该属性的自定义的对比查询的字段名称 in 数据源列表对象的属性集合 && 查询选择器中的该属性是查询的条件  &&  查询选择器该属性的值!=null或者""
32                 if (Array.Exists(dataSource.Properties, p => p.Name == propertySearchAttributeName) && propertySearchAttribute.Operation!=OperationType.None && propertySearchVlaue != null && propertySearchVlaue != (object)string.Empty)
33                 {
34                     var propertyReference = Expression.Property(pe, propertySearchAttributeName);
35                     var sourcePropertyType = dataSource.Properties.FirstOrDefault(p => p.Name == propertySearchAttributeName).PropertyType;  //获取数据源列表元素对象的单个属性的属性类型
36                     ConstantExpression constantReference = null;
37                     Expression Expr = null;
38 
39                     bool isGenericType = sourcePropertyType.IsGenericType && sourcePropertyType.GetGenericTypeDefinition() == typeof(Nullable<>);  //搜索sourcePropertyType是否可为空
40                     if (isGenericType)
41                         constantReference = Expression.Constant(Convert.ChangeType(propertySearchVlaue, Nullable.GetUnderlyingType(sourcePropertyType)), sourcePropertyType);  //如果可为空类型,则将propertySearchVlaue的类型设置为可为空类型
42                     else
43                         constantReference = Expression.Constant(Convert.ChangeType(propertySearchVlaue, sourcePropertyType));
44 
45                     //根据查询选择器中该属性的查询条件进行不同的操作
46                     switch (propertySearchAttribute.Operation)
47                     {
48                         case OperationType.Equal:
49                             Expr = Expression.Equal(propertyReference, constantReference);
50                             break;
51                         case OperationType.GreaterThan:
52                             Expr = Expression.GreaterThan(propertyReference, constantReference);
53                             break;
54                         case OperationType.LessThan:
55                             Expr = Expression.LessThan(propertyReference, constantReference);
56                             break;
57                         case OperationType.GreaterThanOrEqual:
58                             Expr = Expression.GreaterThanOrEqual(propertyReference, constantReference);
59                             break;
60                         case OperationType.LessThanOrEqual:
61                             Expr = Expression.LessThanOrEqual(propertyReference, constantReference);
62                             break;
63                         case OperationType.Like:
64                             Expr = Expression.Call(propertyReference, typeof(String).GetMethod("Contains", new Type[] { typeof(string) }), constantReference);
65                             break;
66                         default:break;
67 
68                     }
69 
70                     expression = Expression.AndAlso(expression, Expr);  //最终的查询条件
71                 }
72             }
73             return Expression.Lambda<Func<TSource, bool>>(expression, pe);
74         }
75     }
注意:必须将Query类和该类的成员方法定义为static,具体原因请搜索C#拓展方法的定义
最后,只需要在IQueryable对象上调用Search(查询条件对象)这个函数并传入参数就可以了

这是我自己定义的查询条件类
 1 public class ProjectInfoDTO
 2     {
 3         [Search("CompanyName",OperationType.Like)]
 4         public string CompanyName { get; set; }
 5 
 6         
 7         [Search("SYS_CreateTime",OperationType.GreaterThanOrEqual)]
 8         public DateTime? CreateTimeStart { get; set; }
 9 
10         [Search("SYS_CreateTime", OperationType.LessThanOrEqual)]
11         public DateTime? CreateTimeEnd { get; set; }
12     }

类的属性名称不一定要与属性上面Search方法的displayName参数的值相同,但是displayName参数的值必须与查询列表对象中属性的名称相同

这是我查询的列表
1 var result = (from a in db.Company_BasicInfo.Where(p => p.CompanyID > 0)
2                               select new
3                               {
4                                   a.CompanyID,
5                                   a.CompanyName,
6                                   a.SYS_CreateTime
7                               }).Search(searchOption).OrderByDescending(p => p.SYS_CreateTime).Take(10000).ToList();

 OK,现在我不需要管我的查询条件是什么了,只需要往查询属性的对象中传入对应的值即可