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

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

问题描述

 表达式< Func< MyObject,string>> fn1 = x => x.PossibleSubPath.MyStringProperty; 

表达式< Func< string,bool>> fn2 = x => x.Contains(some literal);

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

 表达式< Func< MyObject,bool>> fnCombined = ... 

我知道我可以一次创建功能,但问题是我正在制作一些通用代码,因此真的需要能够单独创建这两个函数,然后将它们组合起来,使Linq可以在我的数据库对象(Entity Framework)上使用它们。

解决方案

在逻辑上,我们想要做的是创建一个新的lambda,其中具有第一个函数的输入参数,使用该参数调用第一个函数,然后将结果作为参数传递给第二个函数,然后返回。



我们可以使用表达式对象:

  public static Expression&FunC< T1,T3>组合< T1,T2,T3>(
表达式<功能< T1,T2>>首先
表达式< Func< T2,T3>>秒)
{
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提供此相关问题的链接它提供了一个 ReplaceVisitor 实现。我们可以使用 ReplaceVisitor 来遍历表达式的整个树,并将另一个表达式替换为另一个表达式。该类型的实现是:

  class ReplaceVisitor:ExpressionVisitor 
{
private readonly Expression from,to ;
public ReplaceVisitor(Expression from,Expression to)
{
this.from = from;
this.to = to;
}
public override表达式访问(表达式节点)
{
return node == from? to:base.Visit(node);
}
}

现在我们可以写我们的 / em> 合并方法:

  public static Expression< Func< T1 ,T3>>组合< T1,T2,T3>(
表达式<功能< T1,T2>>首先
表达式< Func< T2,T3>>秒)
{
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);

返回Expression.Lambda< Func< T1,T3>(newSecond,param);
}

和一个简单的测试用例,只是展示了发生了什么:

 表达式< Func< MyObject,string>> fn1 = x => x.PossibleSubPath.MyStringProperty; 
表达式< Func< string,bool>>> fn2 = x => x.Contains(some literal);

var composite =组合(fn1,fn2);

Console.WriteLine(composite);

哪些将打印出来:


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


哪些是我们想;查询提供者将会知道如何解析这样的东西。


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

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

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 = ...

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).

解决方案

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.

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);
}

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.

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.

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);
    }
}

And now we can write our proper Combine method:

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 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 = Combine(fn1, fn2);

Console.WriteLine(composite);

Which will print out:

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天全站免登陆