原文:
Linq to Sql : 动态构造Expression进行动态查询
前一篇在介绍动态查询时,提到一个问题:如何根据用户的输入条件,动态构造这个过滤条件表达式呢?
Expression<Func<ProductExt, bool>> predicate t => t.ProductName.Contains("che") && t.UnitPrice >= 22;
理想情况下,我希望可以像下面这样来构造predicate,这样,我们就可以使用&、| 、&=、|=来任意拼接过滤条件了:
1: Expression<Func<ProductExt, bool>> predicate = null;
2: predicate &= (t => t.ProductName.Contains("che")) | (t => t.UnitPrice >= 22);
但是理想与现实之间,似乎总有不可逾越的鸿沟……
前面的代码中,我们总是要写一常串Expression<Func<T, bool>>,写得都有点儿烦了,我妄想自定义一个类型,这样就不用每次都写这么长了,然后再针对这个自定义类型再进行运算符重载……然后我发现,Expression<Delegate>是密封类型(sealed),不给重载,没办法,只好老老实实的写,运算符重载也泡汤了。
无奈之下,只好实现了下面的这种方式,勉强凑合用着:
1: Expression<Func<ProductExt, bool>> predicate = null;
2: predicate = predicate.AndAlso(p => p.CompanyName.Length > 2)
3: .OrElse((Products p) => p.ProductName.Contains("che"))
4: .AndAlso((Products p) => p.UnitPrice >= 22);
运算得到的结果如下:p => (((p.CompanyName.Length > 2) || p.ProductName.Contains("che")) && (p.UnitPrice >= 22))
有了这个OrElse和AndAlso扩展,我们就可以对Expression<Func<T, bool>>为所欲为了……
不过跟之前文章中提到的一样,这里有个限制:虽然我们的Expression中,第一个可以凡泛型参数可以传任意值(譬如传ProductExt或Products或其他的,可以不必要求是同一种类型),但条件中用到的对象属性(譬如CompanyName、ProductName、UnitPrice ),必须在T中存在同名属性。
2010-12-31更新:
更新了ParameterConverter类,之前对二元运算符的右侧和方法参数直接求值的,所以不能处理Expression中含有&& 和 || 等二元运算符、及方法参数中包含Expression参数(例如list.Contains(p.CompanyName))的情况;现在增加了参数访问计数,如果二元表达式&&和||、及方法的参数中不包含Expression参数,则进行求值,否则不求值。代码如下:
1
public
class
ParameterConverter : ExpressionVisitor
2
{
3
protected
LambdaExpression SourceExpression {
get
;
set
; }
4
protected
ParameterExpression Parameter {
get
;
set
; }
5
protected
bool
UseOuterParameter
=
false
;
6
7
public
ParameterConverter(LambdaExpression expression)
8
{
9
this
.SourceExpression
=
(LambdaExpression)expression;
10
}
11
12
public
ParameterConverter(LambdaExpression expression, ParameterExpression parameter) :
this
(expression)
13
{
14
this
.Parameter
=
parameter;
15
this
.UseOuterParameter
=
true
;
16
}
17
18
public
LambdaExpression Replace(Type targetType)
19
{
20
if
(
this
.SourceExpression
==
null
)
21
return
null
;
22
23
if
(
!
this
.UseOuterParameter)
24
this
.Parameter
=
Expression.Parameter(targetType,
this
.SourceExpression.Parameters[
0
].Name);
25
26
Expression body
=
this
.Visit(
this
.SourceExpression.Body);
27
this
.SourceExpression
=
Expression.Lambda(body,
this
.Parameter);
28
return
this
.SourceExpression;
29
}
30
31
protected
override
Expression VisitParameter(ParameterExpression p)
32
{
33
return
this
.Parameter;
34
}
35
36
protected
override
Expression VisitMemberAccess(MemberExpression m)
37
{
38
Expression exp
=
this
.Visit(m.Expression);
39
PropertyInfo propertyInfo
=
m.Member
as
PropertyInfo;
40
return
propertyInfo
==
null
?
m : Expression.Property(exp, propertyInfo.Name);
41
}
42
43
protected
override
Expression VisitBinary(BinaryExpression b)
44
{
45
Expression left
=
this
.Visit(b.Left);
46
Expression right
=
Calc(b.Right);
//
对二元运算符右边的表达式进行求值
47
Expression conversion
=
this
.Visit(b.Conversion);
48
if
(b.NodeType
==
ExpressionType.Coalesce
&&
b.Conversion
!=
null
)
49
return
Expression.Coalesce(left, right, conversion
as
LambdaExpression);
50
else
51
return
Expression.MakeBinary(b.NodeType, left, right, b.IsLiftedToNull, b.Method);
52
}
53
54
protected
override
ReadOnlyCollection
<
Expression
>
VisitExpressionList(
55
ReadOnlyCollection
<
Expression
>
original)
56
{
57
if
(original
==
null
||
original.Count
==
0
)
58
return
original;
59
60
//
对参数进行求值运算
61
List
<
Expression
>
list
=
new
List
<
Expression
>
();
62
for
(
int
i
=
0
, n
=
original.Count; i
<
n; i
++
)
63
{
64
list.Add(Calc(original[i]));
//
对调用函数的输入参数进行求值
65
}
66
return
list.AsReadOnly();
67
}
68
69
private
static
Expression Calc(Expression e)
70
{
71
LambdaExpression lambda
=
Expression.Lambda(e);
72
return
Expression.Constant(lambda.Compile().DynamicInvoke(
null
), e.Type);
73
}
74
}
最后,上代码:LinqToSqlExtension
博客园的文件貌似有缓存,删掉重新上传,总是覆盖不了,只好单独上传了 ParameterConverter.rar