【问题标题】:Combine two Linq lambda expressions合并两个 Linq lambda 表达式
【发布时间】:2013-10-17 17:24:54
【问题描述】:
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 可以的方式组合它们在我的数据库对象(实体框架)上使用它们。

【问题讨论】:

  • 这是一个显示表达式操作/组合的答案。 stackoverflow.com/a/9683506/8155
  • @DavidB 感谢您的链接;我无法弄清楚如何用另一个表达式替换一个表达式的所有实例。

标签: c# linq expression


【解决方案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 与第一个 lambda 的新主体。从技术上讲,如果这些表达式有副作用,并且这些参数被多次使用,它们就不一样了,但由于这些将由 EF 查询提供程序解析,它们真的不应该有副作用。

感谢 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 => param.PossibleSubPath.MyStringProperty.Contains("some literal")

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

【讨论】:

  • 如果fn2 多次使用其参数,fn1 将重复多次。希望这要么是微不足道的,要么是由查询提供者智能处理的,但这最终可能会导致多次完成大量工作(类似于局部变量的东西应该可以工作,除非查询提供者可能不支持)。
  • @TimS。是的,我在回答中讨论了一点。老实说,我不认为这是一个主要问题,因为我希望数据库端的查询优化器能够有效地处理这种情况。看起来OP正在研究的那种表达方式也不是问题。很难说。正如你所提到的,一个局部变量几乎肯定无法正确解析。
  • @Servy,您能否更新您的答案以反映,从第一个主体传递参数将不起作用,因为该参数是在单独的表达式范围内定义的,所以当您组合表达式时@ 987654333@ 第二个 x 与第一个中的 x 不同。 (你可能可以更好地表达我想说的话)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-21
相关资源
最近更新 更多