posts - 94, comments - 346, trackbacks - 10, articles - 0
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

对List进行子查询及分组

Posted on 2009-06-06 20:43  faib  阅读(...)  评论(... 编辑 收藏

    在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 == nullreturn 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 == nullreturn list;
10             if (queryBuilder.expressionBuilder.Length == 0return 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 == nullreturn 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 == nullreturn;
 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