结合两个Linq lambda表达式

时间:2013-10-17 17:24:55

标签: c# linq expression

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可以这样的方式组合它们在我的数据库对象(实体框架)上使用它们。

1 个答案:

答案 0 :(得分:24)

逻辑上我们希望能够做的是创建一个新的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表达式时,他们通常只会抛出某种异常。有些可以处理它。从理论上讲,他们需要的所有信息都在那里,如果他们的写作具有强大的功能,那就是它。

然而,我们可以做的是,从概念的角度来看,用我们正在创建的新lambda的参数替换该lambda体中第一个lambda参数的每个实例,然后替换第二个lambda参数的所有实例。第二个lambda与第一个lambda的新身体。从技术上讲,如果这些表达式有副作用,并且这些参数不止一次使用,它们就不会相同,但由于它们将由EF查询提供程序解析,因此它们实际上不应该有副作用。 / p>

感谢David B提供this related question的链接,该链接提供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方法:

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 =&gt; param.PossibleSubPath.MyStringProperty.Contains(“some literal”)

这正是我们想要的;查询提供程序将知道如何解析类似的东西。