一尘不染

结合两个Linq lambda表达式

c#

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


阅读 262

收藏
2020-05-19

共1个答案

一尘不染

因此,从逻辑上讲,我们想要执行的操作是创建一个新的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查询提供程序进行解析,因此它们实际上永远不会具有副作用。

感谢David
B提供了到此相关问题的链接,链接提供了一个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 => param.PossibleSubPath.MyStringProperty.Contains(“某些文字”)

这正是我们想要的;查询提供者将知道如何解析类似的内容。

2020-05-19