这可以通过实现一个 Compose 方法来完成,该方法将采用两个表达式并返回一个表达式,就像它会调用第一个表达式一样,然后将其作为参数提供给第二个:
void Search(IQueryable<Entity> results, string keywords,
Expression<Func<Entity, string>> selector)
{
results = results.Where(selector.Compose(obj => obj.Contains(keywords)));
}
为了实现这一点,我们将从一个帮助方法开始,它允许我们用另一个表达式替换一个表达式的所有实例:
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
使用该工具,只需将少量替换内容重新组合到 lambda 中即可:
public static Expression<Func<TFirstParam, TResult>>
Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
将查询过滤到给定字段中包含包含所有单词的单个字符串的项目似乎也很奇怪。您似乎更有可能想要获取包含 any 字符串列表的项目。那是不同的,只需要多做一点工作。
我们可以使用一个称为PredicateBuilder 的新类来构建一个过滤器,该过滤器采用一组其他过滤器的逻辑OR。
void Search(IQueryable<Entity> results, IEnumerable<string> keywords,
Expression<Func<Entity, string>> selector)
{
var finalFilter = keywords.Aggregate(
PredicateBuilder.False<Entity>(),
(filter, keyword) => filter.Or(
selector.Compose(obj => obj.Contains(keyword))));
results = results.Where(finalFilter);
}
我们可以使用前面定义的Replace 方法来实现这个类,如下所示:
public static class PredicateBuilder
{
public static Expression<Func<T, bool>> True<T>() { return f => true; }
public static Expression<Func<T, bool>> False<T>() { return f => false; }
public static Expression<Func<T, bool>> Or<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
}
public static Expression<Func<T, bool>> And<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var secondBody = expr2.Body.Replace(expr2.Parameters[0], expr1.Parameters[0]);
return Expression.Lambda<Func<T, bool>>
(Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
}
}