使用 Expression.AndAlso() 在 C# 中划分表达式会导致异常
Dividing an Expression in C# using Expression.AndAlso() causes an Exception
在我用 C# 编写的项目中,我发现了一个用于此 linq 方法的巨大谓词:
public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
这个谓词工作得很好,但它的条件太多了,我在理解它之前费了很多功夫。我想让它可读。所以写了几个Expression.
但是我有一个像这样的运行时异常:The dreaded "parameter was not bound in the specified LINQ to Entities query expression" exception
我想试试这个答案,但我还是不明白为什么参数 (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]);
我的问题是理解为什么会出现这个异常,因为最后我又回到了巨大的谓词。
因为您看到的是单个 c
参数,实际上有两个不同的 c
参数(我们称它们为 c1
和 c2
)。因此,当您合并两个表达式时:
c1 => c1.Something && c2.SomethingElse;
CLR 生气了,因为它找不到 c2
。
更糟糕的是,在您编写代码时,您有 三个 c
!
c3 => c1.Something && c2.SomethingElse
这是因为您重建了 expr1("a string")
两次(在 Expression.AndAlso(expr1("a string").Body
和 expr1("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);
在我用 C# 编写的项目中,我发现了一个用于此 linq 方法的巨大谓词:
public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
这个谓词工作得很好,但它的条件太多了,我在理解它之前费了很多功夫。我想让它可读。所以写了几个Expression.
但是我有一个像这样的运行时异常:The dreaded "parameter was not bound in the specified LINQ to Entities query expression" exception
我想试试这个答案,但我还是不明白为什么参数 (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]);
我的问题是理解为什么会出现这个异常,因为最后我又回到了巨大的谓词。
因为您看到的是单个 c
参数,实际上有两个不同的 c
参数(我们称它们为 c1
和 c2
)。因此,当您合并两个表达式时:
c1 => c1.Something && c2.SomethingElse;
CLR 生气了,因为它找不到 c2
。
更糟糕的是,在您编写代码时,您有 三个 c
!
c3 => c1.Something && c2.SomethingElse
这是因为您重建了 expr1("a string")
两次(在 Expression.AndAlso(expr1("a string").Body
和 expr1("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);