gpt4 book ai didi

linq-to-sql - Linq to SQL 抛出 StackOverflowException

转载 作者:行者123 更新时间:2023-12-04 11:59:49 25 4
gpt4 key购买 nike

我正在使用 Linq to SQL 执行一个非常简单的查询。我正在创建表达式,然后将其传递给 Where() 扩展方法。当我尝试实际执行查询时,Linq 内部会抛出 StackOverflowException。这是代码:

int expectedCount = 4;
Expression<Func<Thing, bool>> expression = ...;

//Expression looks like (LocaleID = 1 && GenderID ==1 && (TimeFrameID == 2007 || TimeFrameID == 2008))

using (XYZDataContext context = new XYZDataContext())
{
int count = context.Things.Where(expression).Count();
//...
}

这是表达式的 DebugView:
.Lambda #Lambda1<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>(XYZ.DataAccess.Thing $o)
{
.Invoke (.Lambda #Lambda2<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>)($o) & .Invoke (.Lambda #Lambda3<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>)($o)
}

.Lambda #Lambda2<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>(XYZ.DataAccess.Thing $o)
{
.Invoke (.Lambda #Lambda4<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>)($o) & .Invoke (.Lambda #Lambda5<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>)($o)
}

.Lambda #Lambda3<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>(XYZ.DataAccess.Thing $o)
{
.Invoke (.Lambda #Lambda6<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>)($o) | .Invoke (.Lambda #Lambda7<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>)($o)
}

.Lambda #Lambda4<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>(XYZ.DataAccess.Thing $o)
{
$o.LocaleID == .Constant<System.Nullable`1[System.Int32]>(1)
}

.Lambda #Lambda5<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>(XYZ.DataAccess.Thing $o)
{
$o.GenderID == .Constant<System.Nullable`1[System.Int32]>(1)
}

.Lambda #Lambda6<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>(XYZ.DataAccess.Thing $o)
{
$o.TimeframeID == .Constant<System.Nullable`1[System.Int32]>(2007)
}

.Lambda #Lambda7<System.Func`2[XYZ.DataAccess.Thing,System.Boolean]>(XYZ.DataAccess.Thing $o)
{
$o.TimeframeID == .Constant<System.Nullable`1[System.Int32]>(2008)
}

这个表达对我来说似乎是正确的,而且相当微不足道。当我阅读调试 View 时,我看到:
((LocaleID == 1 && GenderID == 1) && (TimeFrameID == 2007 || TimeFrameID == 2008))

...哪个是对的。

更新 1

删除 一个 在内部 or'd 子句中,它工作正常。因此,同时拥有内部 or'd 子句会以某种方式破坏从 LINQ 到 SQL 的转换。

更新 2

我无法让调试器进入 .NET Framework 代码 - 我尝试使用 Reflector 以及 Visual Studio 来执行此操作。我进去过一次,但一般来说是行不通的。我确实进入 StackOverflowException 的一次发生在:
ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, object state, bool ignoreSyncCtx)

更新 3

这是用于创建表达式的代码。有太多代码要发布,但它的核心在下面。我有允许我构建复杂的多级查询并将其序列化为 JSON 和 XML 的类。在核心,查询的每个部分都是使用以下方法构建的,然后是 Or'd 和 And'd 一起:
public class LinqSearchField<T, V> : ISearchField
{
public string Name { get; private set; }
public Expression<Func<T, V>> Selector { get; private set; }

public Expression<Func<T, bool>> LessThan(V value)
{
return Expression.Lambda<Func<T, bool>>(Expression.LessThan(this.Selector.Body, GetConstant(value)), this.Selector.Parameters);
}

public Expression<Func<T, bool>> LessThanOrEqual(V value)
{
return Expression.Lambda<Func<T, bool>>(Expression.LessThanOrEqual(this.Selector.Body, GetConstant(value)), this.Selector.Parameters);
}

public Expression<Func<T, bool>> Equal(V value)
{
return Expression.Lambda<Func<T, bool>>(Expression.Equal(this.Selector.Body, GetConstant(value)), this.Selector.Parameters);
}

public Expression<Func<T, bool>> NotEqual(V value)
{
return Expression.Lambda<Func<T, bool>>(Expression.NotEqual(this.Selector.Body, GetConstant(value)), this.Selector.Parameters);
}

public Expression<Func<T, bool>> GreaterThan(V value)
{
return Expression.Lambda<Func<T, bool>>(Expression.GreaterThan(this.Selector.Body, GetConstant(value)), this.Selector.Parameters);
}

public Expression<Func<T, bool>> GreaterThanOrEqual(V value)
{
return Expression.Lambda<Func<T, bool>>(Expression.GreaterThanOrEqual(this.Selector.Body, GetConstant(value)), this.Selector.Parameters);
}

private ConstantExpression GetConstant(V value)
{
return Expression.Constant(value, typeof(V));
}

public Expression<Func<T, bool>> Null()
{
return Expression.Lambda<Func<T, bool>>(Expression.Equal(this.Selector.Body, Expression.Constant(null)), this.Selector.Parameters);
}

public Expression<Func<T, bool>> NotNull()
{
return Expression.Lambda<Func<T, bool>>(Expression.NotEqual(this.Selector.Body, Expression.Constant(null)), this.Selector.Parameters);
}
}

这是 And 代码(OR 代码相同,但使用 Expression.And 代替):
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
{
ParameterExpression[] parameters = expression1.Parameters.Union(expression2.Parameters).Distinct(new ParameterExpressionComparer()).ToArray();
InvocationExpression invocationExpression1 = Expression.Invoke(expression1, parameters);
InvocationExpression invocationExpression2 = Expression.Invoke(expression2, parameters);
Expression binaryExpression = null;

//And the current expression to the previous one.
binaryExpression = Expression.AndAlso(invocationExpression1, invocationExpression2); //Or OrElse.

//Wrap the expression in a lambda.
return Expression.Lambda<Func<T, bool>>(binaryExpression, parameters);
}

更新 4

它可能会不受欢迎,但这里有一个 sample which reproduces this issue .我真的需要弄清楚这里发生了什么。

最佳答案

我最初有我的怀疑,但现在可以确认它。

您正在组合两个具有两个完全不同的参数实例的 lambda。参数实例是不可交换的,即使它们具有相同的名称和相同的类型。它们是不同范围内的有效参数。当您尝试使用错误的参数对象调用其中一个表达式时,就会出现困惑,在这种情况下,会出现堆栈溢出。

您应该做的是创建一个新的参数实例(或重用一个)并重新绑定(bind) lambda 的主体以使用该新参数。我怀疑这会解决这个问题。更进一步,您应该通过重建它们来正确组合这些表达式,而不是将它们作为方法调用修补在一起。我怀疑查询提供者是否会喜欢这些作为调用。

试试你的 And() 的这个实现和 Or()方法与此辅助方法一起进行重新绑定(bind):

public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
{
// reuse the first expression's parameter
var param = expression1.Parameters.Single();
var left = expression1.Body;
var right = RebindParameter(expression2.Body, expression2.Parameters.Single(), param);
var body = Expression.AndAlso(left, right);
return Expression.Lambda<Func<T, bool>>(body, param);
}

public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
{
var param = expression1.Parameters.Single();
var left = expression1.Body;
var right = RebindParameter(expression2.Body, expression2.Parameters.Single(), param);
var body = Expression.OrElse(left, right);
return Expression.Lambda<Func<T, bool>>(body, param);
}

private static Expression RebindParameter(Expression expr, ParameterExpression oldParam, ParameterExpression newParam)
{
switch (expr.NodeType)
{
case ExpressionType.Parameter:
var asParameterExpression = expr as ParameterExpression;
return (asParameterExpression.Name == oldParam.Name)
? newParam
: asParameterExpression;
case ExpressionType.MemberAccess:
var asMemberExpression = expr as MemberExpression;
return asMemberExpression.Update(
RebindParameter(asMemberExpression.Expression, oldParam, newParam));
case ExpressionType.AndAlso:
case ExpressionType.OrElse:
case ExpressionType.Equal:
case ExpressionType.NotEqual:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
var asBinaryExpression = expr as BinaryExpression;
return asBinaryExpression.Update(
RebindParameter(asBinaryExpression.Left, oldParam, newParam),
asBinaryExpression.Conversion,
RebindParameter(asBinaryExpression.Right, oldParam, newParam));
case ExpressionType.Call:
var asMethodCallExpression = expr as MethodCallExpression;
return asMethodCallExpression.Update(
RebindParameter(asMethodCallExpression.Object, oldParam, newParam),
asMethodCallExpression.Arguments.Select(arg =>
RebindParameter(arg, oldParam, newParam)));
case ExpressionType.Invoke:
var asInvocationExpression = expr as InvocationExpression;
return asInvocationExpression.Update(
RebindParameter(asInvocationExpression.Expression, oldParam, newParam),
asInvocationExpression.Arguments.Select(arg =>
RebindParameter(arg, oldParam, newParam)));
case ExpressionType.Lambda:
var asLambdaExpression = expr as LambdaExpression;
return Expression.Lambda(
RebindParameter(asLambdaExpression.Body, oldParam, newParam),
asLambdaExpression.Parameters.Select(param =>
(ParameterExpression)RebindParameter(param, oldParam, newParam)));
default:
// you should add cases for any expression types that have subexpressions
return expr;
}
}

重新绑定(bind)方法的作用是搜索(按名称)并返回一个表达式,其中所有 ParameterExpression在表达式树中被另一个 ParameterExpression 的实例替换.这不会修改现有表达式,但会在需要时重建表达式以创建新更新的表达式。换句话说,它返回一个新的表达式,它应该被用来替换你正在重新绑定(bind)的那个。

这个想法是检查 Expression并确定它是什么类型。如果是 ParameterExpression ,检查它是否与我们要查找的参数同名。如果是,则返回我们的新参数,否则返回它,因为我们不应该更改它。如果表达式不是参数,它可能是一个包含子表达式并且必须被替换的表达式。

一个 BinaryExpression将有 Left操作数和 Right操作数,两个表达式。它们都需要被反弹,因为它们的表达式树的某个地方可能是需要替换的参数。 Update()方法将用具有新子表达式的类似表达式替换当前表达式。在这种情况下,我们只想(可能)更新 LeftRight子表达式。
MethodCallExpressionInvocationExpression有相同的想法,但它的树略有不同。它有 Object表示您要调用的实例(或委托(delegate)/lambda)的表达式(或 Expression 在调用的情况下)。 ( MethodCallExpression 还有一个 MethodInfo 代表要调用的实例方法)他们也有 Arguments (所有表达式)用作调用的参数。这些表达可能需要被反弹。

你可以想到 RebindParameter()方法作为“ super ”- Update()更新整个表达式树中的参数的方法。

为了进一步说明,帮助可视化树的外观和变化的插图。请注意,由于此处发生了替换,因此大多数子树将是新实例。

[ illustration

现在这是我没有意识到可用的东西, ExpressionVisitor .希望我能早点注意到它。这将使重新绑定(bind)器更好地使用。而不是在这里发布完整的代码,这里是 pastebin .然后使用它:
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
{
// reuse the first expression's parameter
var param = expression1.Parameters.Single();
var left = expression1.Body;
var right = ParameterRebinder.Rebind(expression2.Body, expression2.Parameters.Single(), param);
var body = Expression.AndAlso(left, right);
return Expression.Lambda<Func<T, bool>>(body, param);
}

public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2)
{
var param = expression1.Parameters.Single();
var left = expression1.Body;
var right = ParameterRebinder.Rebind(expression2.Body, expression2.Parameters.Single(), param);
var body = Expression.OrElse(left, right);
return Expression.Lambda<Func<T, bool>>(body, param);
}

关于linq-to-sql - Linq to SQL 抛出 StackOverflowException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5744764/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com