Linq to SQL throws a StackOverflowException

I am doing a fairly simple query using Linq to SQL. I create an expression and then pass it to the Where () extension method. Linq inner elements throw a StackOverflowException when I try to execute a request. 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 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 seems right to me, and it's pretty trivial. When I read the debug view, I see:

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

... what is right.

Update 1

Removing one or'd internal clause, it works great. Therefore, having both internal and standard sentences, it breaks the translation from LINQ to SQL. Somehow.

Update 2

I'm having trouble getting the debugger to go into the .NET Framework code. I tried using Reflector for this as well as for Visual Studio. I logged in once, but generally joining does not work. One time that I got in StackOverflowException occurred 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 too much code to publish, but its heart is lower. I have classes that allow me to create a complex multi-level query and serialize it in JSON and XML. Essentially, each part of the query is built using the following methods, and then 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):

 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

This will probably be disapproving, but here is an example that reproduces this problem . I really need to find out what's going on here.

+7
source share
2 answers

At first I had my suspicions, but now I can confirm this.

You combine two lambdas that have two completely different instances of their parameters. Parameter instances cannot be replaced, even if they have the same name and type. They are effective parameters in different areas. When you tried to call one of the expressions with the wrong parameter object, in this case there is chaos, stack overflow.

What you need to do is instantiate a new parameter (or reuse it) and retrain your lambda bodies to use this new parameter. I suspect that will fix it. And to take a step further, you must correctly combine these expressions, restoring them, instead of binding them together as method calls. I doubt that query providers will like these calls in any way.

Try this implementation of your And() and Or() methods along with this helper method for recovery:

 public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expression1, Expression<Func<T, bool>> expression2) { // reuse the first expression 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 rewriting method does is search (by name) and return an expression in which all ParameterExpression in the expression tree are replaced with an instance of another ParameterExpression . This does not modify existing expressions, but if necessary restores the expression that creates new updated expressions. In other words, it returns a new expression that should be used as a replacement for the one you are redoing.

The idea is to study Expression and determine what type it is. If it is a ParameterExpression , check to see if it has the same name as the parameter we are looking for. If so, return our new parameter, otherwise return it, since we should not change it. If the expression is not a parameter, it is likely to be an expression that contains subexpressions and needs to be replaced.

A BinaryExpression will have a Left operand and a Right operand, both expressions. Both of them should be a rebound, since somewhere down their expression trees may be a parameter that needs to be replaced. The Update() method will replace the current expression with the same with the new subexpressions. In this case, we wanted to (possibly) update the Left and Right subexpressions.

MethodCallExpression and InvocationExpression have the same idea, but the tree is a little different. It has an Object expression (or Expression in case of a call) that represents the instance (or delegate / lambda) that you want to call. ( MethodCallExpression also has MethodInfo , which represents the instance method to call). They also have Arguments (all expressions) that are used as arguments to invoke. These expressions should potentially be restored.

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

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

[ illustration


Now there was something that I did not understand, was ExpressionVisitor . I wish I had noticed this before. This will improve the work of the picker. Instead of publishing 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 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); } 
+7
source

After looking at the information you provided, I am a little puzzled. If you want humor to shoot 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 would start to suspect the side effects of the gette methods of the Thing method. Maybe some kind of interaction leads to recursion?

Are you using Mono by accident?

Not that this was not possible, but I would be very surprised if this is a LinqToSQL provider error.

0
source

All Articles