组合两个 Linq lambda 表达式 [英] Combine two Linq lambda expressions

查看:38
本文介绍了组合两个 Linq lambda 表达式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Expression<Func<MyObject, string>> fn1 = x => x.PossibleSubPath.MyStringProperty;

Expression<Func<string, bool>> fn2 = x => x.Contains("some literal");

有没有办法创建一个新的 lambda 表达式,它基本上使用 fn1 的输出并将其用作 fn2 的输入?

Is there a way to create a new lambda expression which basically uses the output of fn1 and uses it as input for fn2?

Expression<Func<MyObject, bool>> fnCombined = ...

我知道我可以立即创建函数,但问题是我正在编写一些通用代码,因此确实需要能够分别创建这两个函数,然后以 Linq 可以的方式组合它们在我的数据库对象(实体框架)上使用它们.

I know that I can create the function at once, but the problem is that I'm making some generic code and therefore really need to be able to create these two functions separately, then combine them in such a way that Linq can use them on my database objects (Entity Framework).

推荐答案

所以从逻辑上讲,我们希望能够做的是创建一个新的 lambda,其中它具有第一个函数的输入参数,以及一个主体使用该参数调用第一个函数,然后将结果作为参数传递给第二个函数,然后返回.

So logically what we want to be able to do is create a new lambda in which it has a parameter of the input to the first function, and a body that calls the first function with that parameter and then passes the result as the parameter to the second function, and then returns that.

我们可以使用 Expression 对象轻松复制:

We can replicate that easily enough using Expression objects:

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 表达式时,他们通常只会抛出某种异常.不过有些可以处理.理论上,他们需要的所有信息都在那里,如果他们写得足够健壮的话.

Sadly, EF and most other query providers won't really know what to do with that and won't function properly. Whenever they hit an Invoke expression they generally just throw an exception of some sort. Some can handle it though. In theory all the information they need is there, if they're written with the robustness to get at it.

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

What we can do however is, from a conceptual standpoint, replace every instance of the first lambda's parameter in that lambda's body with the parameter of a new lambda we're creating, and then replace all instances of the second lambda's parameter in the second lambda with the new body of the first lambda. Technically, if these expressions have side effects, and these parameters are used more than once, they wouldn't be the same, but as these are going to be parsed by an EF query provider they really shouldn't ever have side effects.

感谢 David B 提供了一个链接到 这个相关问题,它提供了一个 ReplaceVisitor执行.我们可以使用 ReplaceVisitor 遍历表达式的整个树并将一个表达式替换为另一个.该类型的实现是:

Thanks to David B for providing a link to this related question which provides a ReplaceVisitor implementation. We can use that ReplaceVisitor to go through the entire tree of an expression and replace one expression with another. The implementation of that type is:

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 方法:

And now we can write our 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);
}

还有一个简单的测试用例,用来演示正在发生的事情:

and a simple test case, to just demonstrate what's going on:

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("一些文字")

param => param.PossibleSubPath.MyStringProperty.Contains("some literal")

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

Which is exactly what we want; a query provider will know how to parse something like that.

这篇关于组合两个 Linq lambda 表达式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆