因此,从逻辑上讲,我们希望能够创建一个新的 lambda,其中它具有第一个函数的输入参数,以及使用该参数调用第一个函数的主体,然后将结果作为参数传递给第二个函数,然后返回该函数。
我们可以使用以下方法轻松复制这一点Expression
对象:
public static Expression<Func<T1, T3>> Combine<T1, T2, T3>(
Expression<Func<T1, T2>> first,
Expression<Func<T2, T3>> second)
{
var param = Expression.Parameter(typeof(T1), "param");
var body = Expression.Invoke(second, Expression.Invoke(first, param));
return Expression.Lambda<Func<T1, T3>>(body, param);
}
遗憾的是,EF 和大多数其他查询提供程序并不真正知道如何处理它,并且无法正常运行。每当他们击中Invoke
表达他们通常只是抛出某种异常。一些can不过处理一下。理论上,如果他们编写的信息具有足够的鲁棒性来获取信息,那么他们需要的所有信息都在那里。
然而,从概念的角度来看,我们可以做的是,用我们正在创建的新 lambda 的参数替换该 lambda 主体中第一个 lambda 参数的每个实例,然后替换第二个 lambda 中第二个 lambda 参数的所有实例与第一个 lambda 的新主体。从技术上讲,如果这些表达式有副作用,并且这些参数多次使用,它们就不会相同,但由于这些表达式将由 EF 查询提供程序解析,因此它们实际上不应该有副作用。
感谢 David B 提供链接这个相关问题 https://stackoverflow.com/a/9683506/8155它提供了一个ReplaceVisitor
执行。我们可以用它ReplaceVisitor
遍历表达式的整个树并将一个表达式替换为另一个表达式。该类型的实现是:
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);
}
}
现在我们可以写我们的proper Combine
method:
public static Expression<Func<T1, T3>> Combine<T1, T2, T3>(
this Expression<Func<T1, T2>> first,
Expression<Func<T2, T3>> second)
{
var param = Expression.Parameter(typeof(T1), "param");
var newFirst = new ReplaceVisitor(first.Parameters.First(), param)
.Visit(first.Body);
var newSecond = new ReplaceVisitor(second.Parameters.First(), newFirst)
.Visit(second.Body);
return Expression.Lambda<Func<T1, T3>>(newSecond, param);
}
和一个简单的测试用例,只是为了演示发生了什么:
Expression<Func<MyObject, string>> fn1 = x => x.PossibleSubPath.MyStringProperty;
Expression<Func<string, bool>> fn2 = x => x.Contains("some literal");
var composite = fn1.Combine(fn2);
Console.WriteLine(composite);
将打印出:
param => param.PossibleSubPath.MyStringProperty.Contains("一些文字")
这正是我们想要的;查询提供者将知道如何解析类似的内容。