因此,鉴于这是针对 anIQueryable
而不是 an IEnumerable
,您将需要操作表达式,而不是函数。首先,我们将定义一个字典,其中包含每个运算符的字符串版本,并将其映射到表示该操作的表达式:
var mappings = new Dictionary<string, Expression<Func<int, int, bool>>>()
{
{">", ( Expression<Func<int, int, bool>>)((a,b)=> a > b)},
{"<", ( Expression<Func<int, int, bool>>)((a,b)=> a < b)},
{"==", ( Expression<Func<int, int, bool>>)((a,b)=> a == b)},
};
可以添加其他操作。
还有一个辅助方法,我们将使用它在整个某个表达式中用另一个表达式替换一个表达式的所有实例。
它使用此访问者:
public 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 并传递至Where
:
public static IQueryable<TIn> WhereOperator<TIn, TLeft, TRight>(
this IQueryable<TIn> query,
Expression<Func<TIn, TLeft>> leftSelector,
Expression<Func<TIn, TRight>> rightSelector,
Expression<Func<TLeft, TRight, bool>> operation)
{
var newRightBody = rightSelector.Body.Replace(rightSelector.Parameters[0],
leftSelector.Parameters[0]);
var newOperator = operation.Body.Replace(operation.Parameters[0], leftSelector.Body)
.Replace(operation.Parameters[1], newRightBody);
var lambda = Expression.Lambda<Func<TIn, bool>>(newOperator,
leftSelector.Parameters[0]);
return query.Where(lambda);
}
示例用法:
IQueryable<Tuple<int, int>> query = new[] { Tuple.Create(1, 2) }
.AsQueryable();
var query2 = query.WhereOperator(pair => pair.Item1, pair => pair.Item2
, mappings[">"]);
在这种情况下,我们使用内存中的可查询对象,但正如query2.Expression
将向您展示的那样,这实际上看起来与调用相同:
var query3 = query.Where(pair => pair.Item1 > pair.Item2);