对List进行子查询及分组

时间:2022-09-16 12:47:15

    在FaibClass.Data中,DataModelList<T> : List<T>类有三个方法:Select、Group和Compute,可以对集合进行子查询及分组统计,今天就说一其原理。
    本人也看过DataTable的Select方法,不过才浅得很,感觉很是复杂,所以变通了一下,使用动态编译的方式来解决这个问题。
    首先,这两个方法都有一个共同的参数QueryBuilder,使用其构造方法new QueryBuilder(true),用于在添加查询条件时,添加对应的C#语法语句,再动态的生成代码查询出所要的结果。比如添加条件 
    QueryBuilder qb = new QueryBuilder(true);
    qb.Append(QueryRelation.And, QueryCompare.Greater, TB_BUY_BILL._BUY_TIME, new DateTime(2009, 4, 29));
    qb.Append(QueryRelation.And, QueryCompare.Equal, TB_BUY_BILL._BILL_STATE, true);
时,已经生成了一条C#语句
    {0}.BUY_TIME > DateTime.Parse("2009, 4, 29") and {0}.BILL_STATE == true
    其中的{0}要在代码编译时才发挥作用,用实体对象替换。

    一、Select方法

         ///   <summary>
        
///  获取按照指定的与筛选条件相匹配的子集合。
         
///   </summary>
        
///   <param name="queryBuilder"> 要用来筛选的条件。 </param>
        
///   <returns></returns>
         public  DataModelList < T >  Select(QueryBuilder queryBuilder)
        {
            
object  result  =   new  ExpressionCompiler().ComplieSelect( this , queryBuilder);
            
if  (result  ==   null return   null ;
            
return  (DataModelList < T > )result;
        }

ExpressionCompiler是一个表达示编译类,ComplieSelect的方法如下:
 1           ///   <summary>
 2           ///  编译查询子集合表达示。
 3           ///   </summary>
 4           ///   <param name="list"> 进行筛选的数据集合。 </param>
 5           ///   <param name="queryBuilder"> 过滤查询器。 </param>
 6           ///   <returns></returns>
 7           internal  IDataModelList ComplieSelect(IDataModelList list, QueryBuilder queryBuilder)
 8          {
 9               if  (queryBuilder  ==   null return  list;
10               if  (queryBuilder.expressionBuilder.Length  ==   0 return  list;
11              StringBuilder code  =   new  StringBuilder();
12               string  expression  =  FormatExpression(list.ModelType, queryBuilder);
13              code.Append( @"
14                  public IDataModelList Execute(IList list)
15                  {
16                      //创建子集合对象
17                      IDataModelList result = (IDataModelList)Activator.CreateInstance(list.GetType());
18                      //循环当前集合
19                      foreach(object item in list)
20                      {
21                          BaseModel model = (BaseModel)item;
22                          //判断表达示,如果为真添加到子集合
23                          if ( "   +  expression  +   @" )
24                          {
25                              result.Add(model);
26                          }
27                      }
28                      return result;
29                  }
30               " );
31               // 执行编译后返回结果
32               object  result  =  CompileCode( " ComplieSelect " , code.ToString(), list);
33               if  (result  ==   null return   null ;
34               return  (IDataModelList)result;
35          }

FormatExpression即是将{0}替换为实体对象:
 1           ///   <summary>
 2           ///  格式化表达示。
 3           ///   </summary>
 4           ///   <param name="type"></param>
 5           ///   <param name="queryBuilder"></param>
 6           ///   <returns></returns>
 7           private   string  FormatExpression(Type type, QueryBuilder queryBuilder)
 8          {
 9               // 将'号转为"
10               string  expression  =  queryBuilder.expressionBuilder.ToString().Replace( " ' " " \ "" );
11               // 替换{0}为Model.GetValue
12               foreach  (PropertyInfo pinfo  in  type.GetProperties())
13              {
14                   if  (expression.IndexOf( " {0}. "   +  pinfo.Name)  !=   - 1 )
15                  {
16                       // 布尔
17                       if  (pinfo.PropertyType  ==   typeof (Boolean))
18                      {
19                          expression  =  expression.Replace( " {0}. "   +  pinfo.Name,  " ((bool)model.GetValue(\ ""  + pinfo.Name +  " \ " ) ? 1 : 0) " );
20                      }
21                       // 枚举
22                       else   if  (pinfo.PropertyType.BaseType  ==   typeof (Enum))
23                      {
24                          expression  =  expression.Replace( " {0}. "   +  pinfo.Name,  " (int)model.GetValue(\ ""  + pinfo.Name +  " \ " ) " );
25                      }
26                       else
27                      {
28                          expression  =  expression.Replace( " {0}. "   +  pinfo.Name,  " ( "   +  pinfo.PropertyType  +   " )model.GetValue(\ ""  + pinfo.Name +  " \ " ) " );
29                      }
30                  }
31              }
32               return  expression;
33          }

    二、Group方法。
    首先定义了一个类及集合来保存分组的结果:
 1  using  System;
 2  using  System.Collections.Generic;
 3 
 4  namespace  FaibClass.Data
 5  {
 6       ///   <summary>
 7       ///  数据分组集合。
 8       ///   </summary>
 9       public   class  DataGroupCollection : List < DataGroup >
10      {
11           ///   <summary>
12           ///  索引器。
13           ///   </summary>
14           ///   <param name="value"> 分组的值。 </param>
15           ///   <returns></returns>
16           public  DataGroup  this [ object  value]
17          {
18               get  
19              {
20                   foreach  (DataGroup group  in   this )
21                  {
22                       if  (group.Value.Equals(value))
23                           return  group;
24                  }
25                   return   null ;
26              }
27          }
28 
29           ///   <summary>
30           ///  检查分组值是否存在于本集合中。
31           ///   </summary>
32           ///   <param name="value"> 分组的值。 </param>
33           ///   <returns></returns>
34           public   bool  ContainsKey( object  value)
35          {
36               foreach (DataGroup group  in   this )
37              {
38                   if  (group.Value.Equals(value))
39                       return   true ;
40              }
41               return   false ;
42          }
43 
44           ///   <summary>
45           ///  添加一个数据分组。
46           ///   </summary>
47           ///   <param name="value"> 分组的值。 </param>
48           ///   <param name="list"> 分组后的集合。 </param>
49           public   void  Add( object  value, IDataModelList list)
50          {
51              DataGroup group  =   new  DataGroup();
52              group.Value  =  value;
53              group.List  =  list;
54               base .Add(group);
55          }
56      }
57 
58       ///   <summary>
59       ///  数据分组。
60       ///   </summary>
61       public   class  DataGroup
62      {
63           private   object  value;
64           private  IDataModelList list;
65 
66           ///   <summary>
67           ///  值。
68           ///   </summary>
69           public   object  Value
70          {
71               get  {  return  value; }
72               set  {  this .value  =  value; }
73          }
74 
75           ///   <summary>
76           ///  数据集合。
77           ///   </summary>
78           public  IDataModelList List
79          {
80               get  {  return  list; }
81               set  { list  =  value; }
82          }
83      }
84  }
85 

下面是Compute方法的原型:
 1           ///   <summary>
 2           ///  对当前数据集合进行分组。
 3           ///   </summary>
 4           ///   <param name="memberName"> 参照分组的字段名称。 </param>
 5           ///   <returns></returns>
 6           public  DataGroupCollection Group( string  memberName)
 7          {
 8              PropertyInfo pinfo  =  ModelType.GetProperty(memberName, BindingFlags.Public  |  BindingFlags.IgnoreCase  |  BindingFlags.Instance);
 9               if  (pinfo  ==   null )
10              {
11                   throw   new  InvalidColumnException(memberName);
12              }
13              DataGroupCollection coll  =   new  DataGroupCollection();
14               foreach  (T t  in   this )
15              {
16                  BaseModel model  =  (BaseModel)t;
17                   object  value  =  model.GetValue(memberName);
18                   if  ( ! coll.ContainsKey(value))
19                  {
20                      coll.Add(value,  new  DataModelList < T > ());
21                  }
22                  coll[value].List.Add(t);
23              }
24               return  coll;
25          }

    三、Compute方法
    首先是一个计算类别的枚举
 1       ///   <summary>
 2       ///  计算类别。
 3       ///   </summary>
 4       public   enum  ComputeType
 5      {
 6           ///   <summary>
 7           ///  求个数。
 8           ///   </summary>
 9          Count  =   0 ,
10           ///   <summary>
11           ///  求和。
12           ///   </summary>
13          Sum,
14           ///   <summary>
15           ///  求平均值。
16           ///   </summary>
17          Average,
18           ///   <summary>
19           ///  求最大值。
20           ///   </summary>
21          Max,
22           ///   <summary>
23           ///  求最小值。
24           ///   </summary>
25          Min
26      }
一个表达示类
 1       ///   <summary>
 2       ///  计算表达示。
 3       ///   </summary>
 4       public   class  ComputeExpression
 5      {
 6           private  ComputeType countType;
 7           private   string  field;
 8           private   decimal  result;
 9 
10           ///   <summary>
11           ///  构造表达示。
12           ///   </summary>
13           ///   <param name="field"> 要计算的字段名。 </param>
14           ///   <param name="countType"> 计算的类别。 </param>
15           public  ComputeExpression( string  field, ComputeType countType)
16          {
17               this .field  =  field;
18               this .countType  =  countType;
19          }
20 
21           ///   <summary>
22           ///  计算的类别。
23           ///   </summary>
24           public  ComputeType ComputeType
25          {
26               get  {  return  countType; }
27               set  { countType  =  value; }
28          }
29 
30           ///   <summary>
31           ///  要计算的字段名。
32           ///   </summary>
33           public   string  Field
34          {
35               get  {  return  field; }
36               set  { field  =  value; }
37          }
38 
39           ///   <summary>
40           ///  返回计算的结果。
41           ///   </summary>
42           public   decimal  Result
43          {
44               get  {  return  result; }
45               set  { result  =  value; }
46          }
47      }

Compute方法原型:
 1           ///   <summary>
 2           ///  计算用来传递筛选条件的当前集合的给定表达式。
 3           ///   </summary>
 4           ///   <param name="expression"> 要计算的表达式。 </param>
 5           ///   <param name="queryBuilder"> 要限制在表达式中进行计算的筛选器。 </param>
 6           public   void  Compute(ComputeExpression[] expression, QueryBuilder queryBuilder)
 7          {
 8               if  (expression  ==   null return ;
 9               new  ExpressionCompiler().ComplieCompute( this , expression, queryBuilder);
10          }
11 

再来看看编译代码的ComplieCompute方法:
 1           ///   <summary>
 2           ///  编译计算统计表达示。
 3           ///   </summary>
 4           ///   <param name="list"></param>
 5           ///   <param name="expressionArray"></param>
 6           ///   <param name="queryBuilder"></param>
 7           internal   void  ComplieCompute(IDataModelList list, ComputeExpression[] expressionArray, QueryBuilder queryBuilder)
 8          {
 9               foreach  (ComputeExpression exp  in  expressionArray)
10              {
11                  PropertyInfo pinfo  =  list.ModelType.GetProperty(exp.Field, BindingFlags.Public  |  BindingFlags.IgnoreCase  |  BindingFlags.Instance);
12                   if  (pinfo  ==   null )
13                  {
14                       throw   new  InvalidColumnException(exp.Field);
15                  }
16                   if  (exp.ComputeType  !=  ComputeType.Count)
17                  {
18                       if  ( ! Utility.IsNumberType(pinfo.PropertyType))
19                      {
20                           throw   new  Exception( " 非数字型字段不能参与计算。 " );
21                      }
22                  }
23              }
24 
25              StringBuilder code  =   new  StringBuilder();
26               string  expression  =   string .Empty;
27               // 没有查询条件,则条件为true
28               if  (queryBuilder  ==   null )
29              {
30                  expression  =   " true " ;
31              }
32               else   if  (queryBuilder.Length  ==   0 )
33              {
34                  expression  =   " true " ;
35              }
36               else
37              {
38                  expression  =  FormatExpression(list.ModelType, queryBuilder);
39              }
40              code.Append( @"
41                  public bool Execute(ComputeExpression[] expressionArray, IList list)
42                  {
43                      int count = 0;
44                      //初始值
45                      foreach(ComputeExpression exp in expressionArray)
46                      {
47                          if (exp.ComputeType == ComputeType.Min)
48                              exp.Result = decimal.MaxValue;
49                          else if (exp.ComputeType == ComputeType.Max)
50                              exp.Result = decimal.MinValue;
51                      }
52                      foreach(object item in list)
53                      {
54                          BaseModel model = (BaseModel)item;
55                          //如果条件为真
56                          if ( "   +  expression  +   @" )
57                          {
58                              foreach(ComputeExpression exp in expressionArray)
59                              {
60                                  switch (exp.ComputeType)
61                                  {
62                                      case ComputeType.Sum:
63                                      case ComputeType.Average:
64                                          exp.Result += (decimal)model.GetValue(exp.Field);
65                                          break;
66                                      case ComputeType.Max:
67                                          exp.Result = Math.Max((decimal)model.GetValue(exp.Field), exp.Result);
68                                          break;
69                                      case ComputeType.Min:
70                                          exp.Result = Math.Min((decimal)model.GetValue(exp.Field), exp.Result);
71                                          break;
72                                  }
73                              }
74                              count ++;
75                          }
76                      }
77                      foreach(ComputeExpression exp in expressionArray)
78                      {
79                          if (exp.ComputeType == ComputeType.Count)
80                              exp.Result = count;
81                          else if (exp.ComputeType == ComputeType.Average)
82                              exp.Result = count == 0 ? 0 : exp.Result / count;
83                      }
84                      return true;
85                  }
86               " );
87              CompileCode( " ComplieCompute " , code.ToString(), expressionArray, list);
88          }
89