Linq to SQL 抛出 StackOverflowException

发布于 2024-11-02 20:29:51 字数 6119 浏览 3 评论 0原文

我正在使用 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)
}

该表达式对我来说似乎是正确的,而且相当简单。当我阅读调试视图时,我看到:

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

...这是正确的。

更新 1

删除 one 内部 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);
}

Update 4

It可能会被人皱起眉头,但这里有一个 重现此问题的示例。我真的需要弄清楚这里发生了什么事。

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();
    //...
}

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

...which is correct.

Update 1

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.

Update 2

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)

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

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

又怨 2024-11-09 20:29:51

我最初有怀疑,但现在可以证实了。

您要组合两个 lambda,这两个 lambda 具有两个完全不同的参数实例。参数实例不可交换,即使它们具有相同的名称和类型。它们是不同范围内的有效参数。当您尝试使用错误的参数对象调用表达式之一时,就会发生混乱,在本例中是堆栈溢出。

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

尝试使用 And()Or() 方法以及此辅助方法来执行重新绑定:

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 的实例。这不会修改现有表达式,而是会在需要时重建表达式以创建新更新的表达式。换句话说,它返回一个新表达式,该表达式应该用作您要重新绑定的表达式的替换。

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

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

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

您可以将 RebindParameter() 方法视为“超级”-Update() 方法,该方法更新整个表达式树中的参数。

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

[illustration


现在有一些我没有意识到的东西可用,ExpressionVisitor。希望我早点注意到它。这将使重新粘合机更好地使用。完整的代码不是在这里发布,而是在 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);
}

I had my suspicions initially but can now confirm it.

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.

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.

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

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.

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.

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.

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.

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.

[illustration


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);
}
我不吻晚风 2024-11-09 20:29:51

看了你提供的信息后,我有点困惑。如果您愿意在黑暗中幽默一下,请尝试以下代码:

using (XYZDataContext context = new XYZDataContext())
{
    var queryableThings = context.Things.AsQueryable();
    var result = queryableThings.Where(expression);
    int count = result.Count();
}

如果这没有揭示任何内容,我会开始怀疑 Thing 实体的属性 getter 方法的副作用。也许某些交互会导致递归?

您偶然使用 Mono 吗?

并不是说这是不可能的,但如果这是 LinqToSQL 提供程序中的错误,我会感到非常惊讶。

After reviewing the information you provided I'm a bit stumped. If you're willing to humor a shot in the dark, try the following code:

using (XYZDataContext context = new XYZDataContext())
{
    var queryableThings = context.Things.AsQueryable();
    var result = queryableThings.Where(expression);
    int count = result.Count();
}

If this doesn't reveal anything I'd start suspecting side-effects of the Thing entity's property getter methods. Maybe some interaction results in a recursion?

Are you using Mono by chance?

Not that it isn't possible but I'd be really surprised if this is a bug in the LinqToSQL provider.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文