使用 Expression.AndAlso() 在 C# 中划分表达式会导致异常

本文关键字:表达式 划分 异常 AndAlso Expression 使用 | 更新日期: 2023-09-27 17:56:53

在我用 C# 编写的项目中,我发现了一个 linq 方法中使用的巨大谓词:

public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);

这个谓词非常有效,但它的条件如此之大,以至于我在理解它之前挣扎了很多。我想让它可读。所以我写了几个表达式。

但是我有一个这样的运行时异常:可怕的"参数未绑定在指定的 LINQ 到实体查询表达式中"异常

我想尝试答案,但我仍然不明白为什么参数(c)是一个问题,请参阅:

// in a method
Func<string, Expression<Func<TEntity, bool>>> expr1 = (query) => return (c) => ... ;
Func<string, Expression<Func<TEntity, bool>>> expr2 = (query) => return  (c) => ... ;
var expr = Expression.AndAlso(expr1("a string").Body, expr2("same string").Body);
return Expression.Lambda<Func<TEntity, bool>>(expr , expr1("a string").Parameters[0]);

我的问题是理解为什么会发生这种异常,因为我终于恢复了巨大的谓词。

使用 Expression.AndAlso() 在 C# 中划分表达式会导致异常

因为您看到单个c参数的地方,实际上有两种不同的c参数(我们称它们为c1c2)。因此,当您合并两个表达式时,您有:

c1 => c1.Something && c2.SomethingElse;

CLR生气了,因为它找不到c2.

更糟糕的是,当你编写代码时,你有三个c

c3 => c1.Something && c2.SomethingElse

这是因为您重建expr1("a string")两次(在Expression.AndAlso(expr1("a string").Bodyexpr1("a string").Parameters[0]中)!

你应该保存它!

var temp1 = expr1("a string");
var temp2 = expr2("same string");
var expr = Expression.AndAlso(temp1.Body, temp2.Body);
// now fix the expr so that it uses the parameters of temp1
return Expression.Lambda<Func<TEntity, bool>>(expr, temp1.Parameters);

举一个明确的例子:

var temp1a = expr1("a string");
var temp1b = expr1("a string");
var temp2 = expr2("same string");
Console.WriteLine(temp1a.Parameters[0] == temp1b.Parameters[0]); // False
Console.WriteLine(temp1a.Parameters[0] == temp2.Parameters[0]); // False

现在。。。我的参数替换器版本:

public class SimpleParameterReplacer : ExpressionVisitor
{
    public readonly ReadOnlyCollection<ParameterExpression> From;
    public readonly ReadOnlyCollection<ParameterExpression> To;
    public SimpleParameterReplacer(ParameterExpression from, ParameterExpression to)
        : this(new[] { from }, new[] { to })
    {
    }
    public SimpleParameterReplacer(IList<ParameterExpression> from, IList<ParameterExpression> to)
    {
        if (from == null || from.Any(x => x == null))
        {
            throw new ArgumentNullException("from");
        }
        if (to == null || to.Any(x => x == null))
        {
            throw new ArgumentNullException("to");
        }
        if (from.Count != to.Count)
        {
            throw new ArgumentException("to");
        }
        // Note that we should really clone from and to... But we will
        // ignore this!
        From = new ReadOnlyCollection<ParameterExpression>(from);
        To = new ReadOnlyCollection<ParameterExpression>(to);
    }
    protected override Expression VisitParameter(ParameterExpression node)
    {
        int ix = From.IndexOf(node);
        if (ix != -1)
        {
            node = To[ix];
        }
        return base.VisitParameter(node);
    }
}

您可以使用 更改单个参数或参数数组...您可以像这样使用它:

var temp1 = expr1("a string");
var temp2 = expr2("same string");
var expr = Expression.AndAlso(temp1.Body, temp2.Body);
expr = new SimpleParameterReplacer(temp2.Parameters, temp1.Parameters).Visit(expr);
return Expression.Lambda<Func<TEntity, bool>>(expr, temp1.Parameters);