在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