Linq to SQL引发StackOverflowException [英] Linq to SQL throwing a StackOverflowException

查看:64
本文介绍了Linq to SQL引发StackOverflowException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm executing a pretty simple query using Linq to SQL. I'm creating the expression and then passing it to the Where() extension method. The Linq internals are throwing a StackOverflowException when I attempt to actually execute the query. Here is the code:

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:

And here is the DebugView of the expression:

.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)
}

这个表达对我来说似乎是正确的,而且是微不足道的.当我阅读调试视图时,我看到:

The expression appears correct to me and it is fairly trivial. When I read the debug view I see:

((LocaleID == 1 && GenderID == 1) && (TimeFrameID == 2007 || TimeFrameID == 2008))

...这是正确的.

删除内部或子句中的一个,效果很好.因此,同时拥有内部或'd子句将以某种方式破坏从LINQ到SQL的转换.

Removing one of the inner or'd clauses, it works fine. So having both inner or'd clauses is breaking the translation from LINQ to SQL, somehow.

我很难让调试器进入.NET Framework代码-我已经尝试使用Reflector以及Visual Studio来做到这一点.我进了一次,但总的来说是行不通的.我确实进入StackOverflowException的那一次发生在:

I'm having trouble getting the debugger to step into .NET Framework code - I've tried using Reflector to do it as well as just Visual Studio. I got in once but in general stepping in is not working. The one time I did get in the StackOverflowException was occurring in:

ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, object state, bool ignoreSyncCtx)

更新3

这是用于创建表达式的代码.有太多代码要发布,但其核心在下面.我有一些类,这些类使我可以构建复杂的多级查询并将其序列化为JSON和XML.基本上,查询的每个部分都是使用以下方法构建的,然后分别进行Or'd和And'd:

Update 3

Here is the code that is used to create the Expression. There is way too much code to post but the heart of it is below. I have classes which allow me to build a complex multi-level query and serialize it to JSON and XML. At the core, each piece of the query is built using the following methods and then is Or'd and And'd together:

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):

Here is the And code (the OR code is the same but with Expression.And instead):

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

它可能会被皱眉,但这是一个复制的示例这个问题.我真的需要弄清楚这里发生了什么.

Update 4

It will probably be frowned upon but here is a sample which reproduces this issue. I really need to figure out what's going on here.

推荐答案

我最初有怀疑,但现在可以确认.

I had my suspicions initially but can now confirm it.

您要组合两个具有两个完全不同的参数实例的lambda.即使参数实例具有相同的名称和相同的类型,它们也不能互换.它们是不同范围内的有效参数.当您尝试使用错误的参数对象调用其中一个表达式时,会导致混乱,在这种情况下,堆栈会溢出.

You're combining two lambdas that have two completely different instances of their parameters. The parameter instances are not swappable, even if they have the same names and same types. They are effectively parameters in different scopes. When you attempted to invoke one of the expressions with the wrong parameter object, chaos ensues, in this case, a stack overflow.

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

What you should be doing is create a new parameter instance (or reuse one) and rebind the bodies of your lambdas to use that new parameter. I suspect that will fix this. And to go a step further, you should properly combine these expressions by rebuilding them, rather than patching them together as method calls. I doubt the query providers will like these as invocations any way.

尝试将此And()Or()方法的实现与该辅助方法一起进行重新绑定:

Try this implementation of your And() and Or() methods along with this helper method to do the rebinding:

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;
    }
}

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

What the rebinding method does is searches for (by name) and returns an expression where all ParameterExpression within an expression tree are replaced with an instance of another ParameterExpression. This does not modify the existing expressions but rebuilds the expression creating newly updated expressions when needed. In other words, it returns a new expression that should be used as a replacement of the one that you are rebinding.

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

The idea is to examine the Expression and determine what type it is. If it is a ParameterExpression, check if it has the same name as the parameter we're looking for. If it is, return our new parameter, otherwise return it as we shouldn't change it. If the expression is not a parameter, it will probably be an expression that contains subexpressions and would have to be replaced.

一个BinaryExpression将具有一个Left操作数和一个Right操作数,这两个表达式都是.它们都需要反弹,因为它们的表达式树下的某个地方可能是需要替换的参数. Update()方法将使用新的子表达式将当前表达式替换为类似的表达式.在这种情况下,我们只想(可能)更新LeftRight子表达式.

A BinaryExpression will have a Left operand and a Right operand, both expressions. They both need to be rebound since somewhere down their expression trees might be a parameter that needs replacing. The Update() method will replace the current expression with a similar one with the new subexpressions. In this case, we only wanted to (potentially) update the Left and Right subexpressions.

MethodCallExpressionInvocationExpression具有相同的想法,但是它的树略有不同.它具有Object表达式(在调用的情况下为Expression),该表达式表示您要调用的实例(或委托/lambda). (MethodCallExpression也有一个MethodInfo,它代表要调用的实例方法).它们还具有Arguments(所有表达式),它们用作调用的参数.这些表情可能需要反弹.

The MethodCallExpression and InvocationExpression has the same idea but it's tree is slightly different. It has the Object expression (or Expression in the case of an invocation) which represents the instance (or delegate/lambda) that you want to be calling on. (The MethodCallExpression also has a MethodInfo which represents the instance method to call) They also have Arguments (all expressions) which are used as the arguments to the call. These expressions potentially would need to be rebound.

您可以将RebindParameter()方法视为一种"super"-Update()方法,该方法可以更新整个表达式树中的参数.

You can think of the RebindParameter() method as a "super"-Update() method which updates parameters within an entire expression tree.

要进一步说明,请使用插图来形象化树的外观和变化.请注意,由于此处发生替换,因此大多数子树都是新实例.

To further illustrate, an illustration to help visualize what the tree looks like and what changes. Note that since there are replacements occurring here, most of the subtrees will be new instances.

[

现在,我没有意识到某些可用的东西, pastebin 上.然后使用它:

Now here's something I didn't realize was available, the ExpressionVisitor. Wish I noticed it sooner. This will make the rebinder better to work with. Rather than posting the full code here, here it is on pastebin. Then to use it:

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引发StackOverflowException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆