好的,这是一个棘手的问题。希望这里有一位表达大师可以发现我在这里做错了什么,因为我只是没有得到它。
我正在构建用于过滤查询的表达式。为了简化这个过程,我有几个Expression<Func<T, bool>>
扩展方法,使我的代码更清晰,到目前为止,它们已经很好地工作了。我已经为所有人编写了测试,除了一个,我今天写了一个。并且该测试完全失败,ArgumentException
带有长堆栈跟踪。我只是不明白。特别是因为我在查询中成功使用了该方法一段时间了!
无论如何,这是运行测试时得到的堆栈跟踪:
failed: System.ArgumentException : An item with the same key has already been added.
at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
at System.Linq.Expressions.ExpressionCompiler.PrepareInitLocal(ILGenerator gen, ParameterExpression p)
at System.Linq.Expressions.ExpressionCompiler.GenerateInvoke(ILGenerator gen, InvocationExpression invoke, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.GenerateBinary(ILGenerator gen, BinaryExpression b, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.GenerateUnliftedAndAlso(ILGenerator gen, BinaryExpression b)
at System.Linq.Expressions.ExpressionCompiler.GenerateAndAlso(ILGenerator gen, BinaryExpression b, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.GenerateBinary(ILGenerator gen, BinaryExpression b, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.GenerateUnliftedOrElse(ILGenerator gen, BinaryExpression b)
at System.Linq.Expressions.ExpressionCompiler.GenerateOrElse(ILGenerator gen, BinaryExpression b, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.GenerateBinary(ILGenerator gen, BinaryExpression b, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.GenerateInvoke(ILGenerator gen, InvocationExpression invoke, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.GenerateUnliftedAndAlso(ILGenerator gen, BinaryExpression b)
at System.Linq.Expressions.ExpressionCompiler.GenerateAndAlso(ILGenerator gen, BinaryExpression b, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.GenerateBinary(ILGenerator gen, BinaryExpression b, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.GenerateLambda(LambdaExpression lambda)
at System.Linq.Expressions.ExpressionCompiler.CompileDynamicLambda(LambdaExpression lambda)
at System.Linq.Expressions.Expression`1.Compile()
PredicateTests.cs(257,0): at Namespace.ExpressionExtensionsTests.WhereWithin_CollectionIsFilteredAsExpected()
测试本身如下所示,它在Compile语句中失败:
[Test]
public void WhereWithin_CollectionIsFilteredAsExpected()
{
var range = new[] { Range.Create(2, 7), Range.Create(15, 18) };
var predicate = Predicate
.Create<int>(x => x % 2 == 0)
.AndWithin(range, x => x)
.Compile();
var actual = Enumerable.Range(0, 20)
.Where(predicate)
.ToArray();
Assert.That(actual, Is.EqualTo(new[] { 2, 4, 6, 16, 18 }));
}
我只是不明白错误信息。我认为这可能与我总是使用x
作为参数名称这一事实有关,但在我尝试交换它们时似乎没有帮助。让我更奇怪的是,我已经在更大的Linq2Sql查询中使用了这个确切的方法已经有一段时间了,并且它们总是很好地工作。所以在我的测试中,我试图不编译表达式并使用AsQueryable
所以我可以使用它来代替。但这只是在ToArray
上发生异常。这里发生了什么?我该如何解决这个问题?
您可以在以下行的zip文件中找到有问题且烦人的代码:
注意: 我在这里发布了一些相关代码,但经过一些评论后,我决定将代码提取到自己的项目中,该项目显示异常更清楚。更重要的是,可以运行,编译和调试。
更新: 使用@Mark的一些建议进一步简化了示例项目。像删除范围类,而只是硬编码单个恒定范围。还添加了另一个示例,其中使用完全相同的方法实际上工作正常因此,使用AndWithin方法会使应用程序崩溃,而使用WhereWithin方法实际上可以正常工作。我感到非常无能为力!
答案 0 :(得分:3)
我重构了你的方法,使编译器更快乐了:
public static Expression<Func<TSubject, bool>> AndWithin<TSubject, TField>(
this Expression<Func<TSubject, bool>> original,
IEnumerable<Range<TField>> range, Expression<Func<TSubject, TField>> field) where TField : IComparable<TField>
{
return original.And(range.GetPredicateFor(field));
}
static Expression<Func<TSource, bool>> GetPredicateFor<TSource, TValue>
(this IEnumerable<Range<TValue>> range, Expression<Func<TSource, TValue>> selector) where TValue : IComparable<TValue>
{
var param = Expression.Parameter(typeof(TSource), "x");
if (range == null || !range.Any())
return Expression.Lambda<Func<TSource, bool>>(Expression.Constant(false), param);
Expression body = null;
foreach (var r in range)
{
Expression<Func<TValue, TValue, TValue, bool>> BT = (val, min, max) => val.CompareTo(min) >= 0 && val.CompareTo(max) <= 0;
var newPart = Expression.Invoke(BT, param,
Expression.Constant(r.Start, typeof(TValue)),
Expression.Constant(r.End, typeof(TValue)));
body = body == null ? newPart : (Expression)Expression.OrElse(body, newPart);
}
return Expression.Lambda<Func<TSource, bool>>(body, param);
}
两者都有IComparable<TValue>
的附加限制(对第一种方法的唯一更改)。
在第二个中,我通过Func
表达式实现进行比较,注意func是在循环内部创建的......这是第二次添加(它认为是相同的......) 。)旧方法中的表达正在爆炸。
免责声明:我仍然不完全理解为什么您之前的方法不起作用,但这种替代方法绕过了问题。如果这不是您所追求的,请告诉我们,我们会尝试别的。
此外,对问题问题的称赞,一个示例项目就是典范。
答案 1 :(得分:2)
这不是答案,但我希望它能帮助别人找到答案。我已经进一步简化了代码,因此它只是一个文件,但仍然以同样的方式失败。我已重命名变量,以便“x”不会被使用两次。我删除了Range类,并用硬编码的常量0和1替换它。
using System;
using System.Linq;
using System.Linq.Expressions;
class Program
{
static Expression<Func<int, bool>> And(Expression<Func<int, bool>> first,
Expression<Func<int, bool>> second)
{
var x = Expression.Parameter(typeof(int), "x");
var body = Expression.AndAlso(Expression.Invoke(first, x), Expression.Invoke(second, x));
return Expression.Lambda<Func<int, bool>>(body, x);
}
static Expression<Func<int, bool>> GetPredicateFor(Expression<Func<int, int>> selector)
{
var param = Expression.Parameter(typeof(int), "y");
var member = Expression.Invoke(selector, param);
Expression body =
Expression.AndAlso(
Expression.GreaterThanOrEqual(member, Expression.Constant(0, typeof(int))),
Expression.LessThanOrEqual(member, Expression.Constant(1, typeof(int))));
return Expression.Lambda<Func<int, bool>>(body, param);
}
static void Main()
{
Expression<Func<int, bool>> predicate = a => true;
predicate = And(predicate, GetPredicateFor(b => b)); // Comment out this line and it will run without error
var z = predicate.Compile();
}
}
表达式在调试器中如下所示:
x => (Invoke(a => True,x) && Invoke(y => ((Invoke(b => b,y) >= 0) && (Invoke(b => b,y) <= 1)),x))
更新:我已将其简化为最简单的,但仍会抛出相同的异常:
using System;
using System.Linq;
using System.Linq.Expressions;
class Program
{
static void Main()
{
Expression<Func<int, bool>> selector = b => true;
ParameterExpression param = Expression.Parameter(typeof(int), "y");
InvocationExpression member = Expression.Invoke(selector, param);
Expression body = Expression.AndAlso(member, member);
Expression<Func<int, bool>> predicate = Expression.Lambda<Func<int, bool>>(body, param);
var z = predicate.Compile();
}
}