在LINQ to Entities 中构建谓词表达式 Predicates
本文将介绍在LINQ to Entities 中3种构建谓词表达式的方法
测试类:
public class Car
{
public float Price { get; set; }
public string Color { get; set; }
}
1.链式操作符 Chaining query operators
条件一:车是红色
条件二:车的价钱小于10.0 想要同时满足以上两个条件,可以使用Where方法链式操作:
Expression<Func<Car, bool>> theCarIsRed = c => c.Color == “Red”;
Expression<Func<Car, bool>> theCarIsCheap = c => c.Price < 10.0;
IQueryable<Car> carQuery = …;
var query = carQuery.Where(theCarIsRed).Where(theCarIsCheap);
如果想要满足以上任意一个条件,可以使用Union方法:
var query2 = carQuery.Where(theCarIsRed).Union(carQuery.Where(theCarIsCheap));
但是Union本身会带来效率问题,并且会消除结果中的一些重复项
2.使用原生API构建表达式 Build expressions manually
LINQ 表达式 API 包含一些工厂方法,我们可以手动调用:
ParameterExpression c = Expression.Parameter(typeof(Car), “car”);
Expression theCarIsRed = Expression.Equal(Expression.Property(c, “Color”), Expression.Constant(“Red”));
Expression theCarIsCheap = Expression.LessThan(Expression.Property(c, “Price”), Expression.Constant(10.0));
Expression<Func<Car, bool>> theCarIsRedOrCheap = Expression.Lambda<Func<Car, bool>>(
Expression.Or(theCarIsRed, theCarIsCheap), c);
var query = carQuery.Where(theCarIsRedOrCheap);
可以看到这种方式很不方便,写起来相当麻烦,因此我们将探索使用第3种方式
3.组合Lambda表达式
C# 3.0 book中介绍了一种组合Lambda表达式的方法:
Expression<Func<Car, bool>> theCarIsRed = c1 => c1.Color == “Red”;
Expression<Func<Car, bool>> theCarIsCheap = c2 => c2.Price < 10.0;
Expression<Func<Car, bool>> theCarIsRedOrCheap = Expression.Lambda<Func<Car, bool>>(
Expression.Or(theCarIsRed.Body, theCarIsCheap.Body), theCarIsRed.Parameters.Single());
var query = carQuery.Where(theCarIsRedOrCheap);
参照Matt Warren的系列文章IQueryable Provider 中包含对ExpressionVisitor 的实现,我们可以重写表达式树,以下是实现参数重新绑定的类:
public class ParameterRebinder : ExpressionVisitor {
private readonly Dictionary<ParameterExpression, ParameterExpression> map;
public ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map) {
this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
}
public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp) {
return new ParameterRebinder(map).Visit(exp);
}
protected override Expression VisitParameter(ParameterExpression p) {
ParameterExpression replacement;
if (map.TryGetValue(p, out replacement)) {
p = replacement;
}
return base.VisitParameter(p);
}
}
现在,我们可以写一个工具类在不使用invoke的情况下实现组合lambda表达式,并且提供EF友好的And和Or方法:
public static class Utility {
public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge) {
// build parameter map (from parameters of second to parameters of first)
var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f);
// replace parameters in the second lambda expression with parameters from the first
var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
// apply composition of lambda expression bodies to parameters from the first expression
return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
}
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) {
return first.Compose(second, Expression.And);
}
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) {
return first.Compose(second, Expression.Or);
}
}
使用lambda表达式组合,我们可以这样写:
Expression<Func<Car, bool>> theCarIsRed = c => c.Color == “Red”;
Expression<Func<Car, bool>> theCarIsCheap = c => c.Price < 10.0;
Expression<Func<Car, bool>> theCarIsRedOrCheap = theCarIsRed.Or(theCarIsCheap);
var query = carQuery.Where(theCarIsRedOrCheap);