在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>
/// <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的方法如下:
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}替换为实体对象:
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方法。
首先定义了一个类及集合来保存分组的结果:
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方法的原型:
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方法
首先是一个计算类别的枚举
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 }
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方法原型:
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方法:
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