天天看點

Linq to Sql : 動态構造Expression進行動态查詢

Expression<Func<ProductExt, bool>> predicate t => t.ProductName.Contains("che") && t.UnitPrice >= 22;

    理想情況下,我希望可以像下面這樣來構造predicate,這樣,我們就可以使用&、| 、&=、|=來任意拼接過濾條件了:

    但是理想與現實之間,似乎總有不可逾越的鴻溝……

    前面的代碼中,我們總是要寫一常串Expression<Func<T, bool>>,寫得都有點兒煩了,我妄想自定義一個類型,這樣就不用每次都寫這麼長了,然後再針對這個自定義類型再進行運算符重載……然後我發現,Expression<Delegate>是密封類型(sealed),不給重載,沒辦法,隻好老老實實的寫,運算符重載也泡湯了。

   無奈之下,隻好實作了下面的這種方式,勉強湊合用着:

    運算得到的結果如下: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     }