Expression<Func<MyObject, string>> fn1 = x => x.PossibleSubPath.MyStringProperty; Expression<Func<string, bool>> fn2 = x => x.Contains("some literal");
是否有一种方法可以创建一个新的lambda表达式,该表达式基本上使用fn1的输出并将其用作fn2的输入?
Expression<Func<MyObject, bool>> fnCombined = ...
我知道我可以立即创建该函数,但是问题是我正在编写一些通用代码,因此确实需要能够分别创建这两个函数,然后以使Linq可以在其上使用它们的方式进行组合我的数据库对象(实体框架)。
因此,从逻辑上讲,我们想要执行的操作是创建一个新的lambda,在其中,它具有第一个函数的输入参数,以及一个主体,该主体使用该参数调用第一个函数,然后将结果作为参数传递给第二个函数,然后返回该函数。
我们可以使用Expression对象轻松地复制它:
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表达式时,它们通常都会抛出某种异常。有些人 可以 应付。从理论上讲,如果编写的信息具有鲁棒性,则它们就在那里。
Invoke
但是,我们可以做的是,从概念上讲,用我们正在创建的新Lambda的参数替换该Lambda主体中的第一个Lambda参数的每个实例,然后替换第二个Lambda中的第二个Lambda参数的所有实例新的第一代lambda。从技术上讲,如果这些表达式具有副作用,并且这些参数被多次使用,它们将是不相同的,但是由于这些将由EF查询提供程序进行解析,因此它们实际上永远不会具有副作用。
感谢David B提供了到此相关问题的链接,该链接提供了一个ReplaceVisitor实现。我们可以使用它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); } }
现在我们可以编写 适当的 Combine方法:
Combine
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(“某些文字”)
这正是我们想要的;查询提供者将知道如何解析类似的内容。