【问题标题】:Error when combining Lambda expressions: "variable 'foo' of type 'Foo' referenced from the scope '', but it is not defined组合 Lambda 表达式时出错:“从范围 '' 引用的 'Foo' 类型的变量 'foo',但未定义
【发布时间】:2018-05-30 13:13:37
【问题描述】:

我正在尝试 combine two lambda expressions 使用 OR 子句构建一些东西,但它失败并显示以下异常消息:

从范围“”引用了“Foo”类型的变量“foo”,但未定义。

为什么,我该如何解决?

这是一个失败的代码示例,基于上面链接的问题的Marc Gravell's answer

static Expression<Func<T, bool>> Or<T>(Expression<Func<T, bool>> a, Expression<Func<T, bool>> b)
    => Expression.Lambda<Func<T, bool>>(Expression.OrElse(a.Body, b.Body), b.Parameters);

static Expression<Func<Foo, bool>> BarMatches(Bar bar) => foo => foo.Bar.Value == bar.Value;
static Expression<Func<Foo, bool>> BazMatches(Baz baz) => foo => foo.Baz.Value == baz.Value;

// sample usage (see below): foos.Where(Or(MatchesBar(bar), MatchesBaz(baz)))

void Main()
{
    var foos = new[]
    {
        new Foo
        {
            Bar = new Bar
            {
                Value = "bar"
            },
            Baz = new Baz
            {
                Value = "baz"
            }
        },
        new Foo
        {
            Bar = new Bar
            {
                Value = "not matching"
            },
            Baz = new Baz
            {
                Value = "baz"
            }
        }
    }.AsQueryable();

    var bar = new Bar { Value = "bar" };
    var baz = new Baz { Value = "baz" };

    Console.WriteLine(foos.Where(Or(BarMatches(bar), BazMatches(baz))).Count());
}


// Define other methods and classes here
class Foo
{
    public Bar Bar { get; set; }
    public Baz Baz { get; set; }
}

class Bar
{
    public string Value { get; set; }
}

class Baz
{
    public string Value { get; set; }
}

【问题讨论】:

  • 与您链接的相同解释了这一点。答案开始:“但问题是参数;你在 expr1 和 expr2 中使用相同的 ParameterExpression 吗?如果是这样,那就更容易了”。您没有使用相同的参数 - 因此此代码不适用于您的情况,您需要进一步阅读该答案。
  • @Evk,谢谢:我试图避免 Expression.Invoke,因为我担心 EF Linq 提供程序无法应对 - 结果证明它工作得很好。如果您想将其写为答案,我可以接受并为您提供一些代表:)
  • 如果您使用的是 EF Core,我会确保它确实将其转换为 SQL,而不仅仅是在内存中调用。但我认为这个问题的真正解决方案是表达式访问者方法,正如在那个答案中再次提到的那样:) 或者像 LinqKit 这样的第三方库,它们会为你做到这一点。

标签: c# linq lambda linq-expressions


【解决方案1】:

问题是BarMatchesBazMatches的参数(foo)不一样。因此,您需要统一参数,以便 Or-ed 表达式使用相同的参数。这可以通过表达式替换器(从this answer 窃取)来完成:

static TExpr ReplaceExpressions<TExpr>(TExpr expression,
                                              Expression orig,
                                              Expression replacement)
where TExpr : Expression 
{
    var replacer = new ExpressionReplacer(orig, replacement);
    return replacer.VisitAndConvert(expression, "ReplaceExpressions");
}

private class ExpressionReplacer : ExpressionVisitor
{
    private readonly Expression From;
    private readonly Expression To;

    public ExpressionReplacer(Expression from, Expression to) {
        From = from;
        To = to;
    }

    public override Expression Visit(Expression node) {
        if (node == From) {
            return To;
        }
        return base.Visit(node);
    }
}

这个类将用另一个表达式替换一个表达式的所有实例。我们现在可以使用它来创建一个统一的表达式:

static Expression<Func<T, bool>> Or<T>(Expression<Func<T, bool>> a, Expression<Func<T, bool>> b) {
    // We know Parameters is exactly of length 1.
    // If you had multiple parameters you would need to invoke this for each parameter
    var replaced = ReplaceExpressions(a.Body, a.Parameters[0], b.Parameters[0]);
    return Expression.Lambda<Func<T, bool>>(Expression.OrElse(replaced, b.Body), b.Parameters);
}

虽然此代码可以满足您的需求,但我建议您使用能够执行此操作的库以及更多功能:LinqKIT

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-09
    • 1970-01-01
    相关资源
    最近更新 更多