次の方法で共有


An unhandled exception of type 'System.StackOverflowException' occurred in System.Data.Entity.dll

I have now had two cases where customers report that they hit the following exception when using .Net 3.5 and Entity Framework.

An unhandled exception of type 'System.StackOverflowException' occurred in System.Data.Entity.dll

or in the eventviewer:

"exception code 0xe053534f"

What these cases have had in common is that they have used a method called BuildContainsExpression in order to get around the
lack of support for the Contains method in the Linq to Entities framework.

"Supported and Unsupported LINQ Methods (LINQ to Entities)"
.Net 4.0 - https://msdn.microsoft.com/en-us/library/bb738550.aspx
.Net 3.5 - https://msdn.microsoft.com/en-us/library/bb738550(VS.90).aspx

What seems to be the issue is that the generated code is producing a lot of OR statements for the SQL. And this will cause the StackOverFlowException.

So, let’s demonstrate:

Create new .Net 3.5 console application.
Add a new .edmx called Northwind.edmx and add just the Region table to it.
Then add the following code to the program:

        static void Main(string[] args)

        {

            using (NorthwindEntities ne = new NorthwindEntities())

            {

                try

                {

                    int range = 5;

           List<int> regionList = new List<int>();

                    regionList.AddRange(Enumerable.Range(1, range));

                    System.Data.Objects.ObjectQuery<Region> r = ne.Region;

 

                    IQueryable<Region> query = r.Where(Program.BuildContainsExpression<Region, int>(Region => Region.RegionID, regionList));

                    foreach (Region region in query)

                    {

                        Console.WriteLine("Id: {0}, Desc: {1}", region.RegionID, region.RegionDescription);

                    }

                }

                catch (Exception ex)

                {

                    Console.WriteLine(ex);

                }

            }

        }

 

        static Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values)

        {

            ParameterExpression p = valueSelector.Parameters.Single();

            if (!values.Any())

            {

                return e => false;

            }

            var equals = values.Select(value => (Expression)Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof(TValue))));

            var body = equals.Aggregate<Expression>((accumulate, equal) => Expression.Or(accumulate, equal));

 

            return Expression.Lambda<Func<TElement, bool>>(body, p);

        }

Start SQL Profiler and run this against the server that holds the Northwind database and then run your console application. You should see the following output:

SELECT
[Extent1].[RegionID] AS [RegionID], [Extent1].[RegionDescription] AS [RegionDescription]
FROM [dbo].[Region] AS [Extent1]
WHERE (1 = [Extent1].[RegionID]) OR (2 = [Extent1].[RegionID]) OR (3 = [Extent1].[RegionID]) OR (4 = [Extent1].[RegionID]) OR (5 = [Extent1].[RegionID])

So you’ll see that for each Region passed an OR clause is added.
Now increase the range to 2000 (on 64 bit machine) or 4000 (on 32 bit machine) and rerun it. This should now give the StackOverFlowException.
The reason it works on 32 bit and not on 64 bit is that on a 64 bit each stackframe is larger but the stacksize for the application is the same.
In short. On 32 bit you may be able to put 100 frames on stack, on 64 you will only be able to put 50. This is because 50x2=100.
What this means is that the exception will be thrown on 32 bit as well, given enough time. In example above 2200 works on 32 bit but not on 64.

But basically the error comes from pushing objects onto the stack thanks to recursion. Eventually causing the overflow.

So, the solution is to use one of the following:
- Use fewer objects in the query (fewer OR clauses).
- Use .Net 4.0 if possible since this will use the IN statement instead of OR, drastically reducing the SQL string in size. If running with range set to 5, output will be:

SELECT
[Extent1].[RegionID] AS [RegionID], [Extent1].[RegionDescription] AS [RegionDescription]
FROM [dbo].[Region] AS [Extent1]
WHERE [Extent1].[RegionID] IN (1,2,3,4,5)

 

- Change stack size for the application via EDITBIN:
  EDITBIN /STACK:4194304 C:\<path>\DemoApp.exe
"/STACK"
https://msdn.microsoft.com/en-us/library/35yc2tc3(VS.90).aspx
- Run the functions in a separate thread with a specified stack size for that thread. This is not recommended.
"Thread Constructor (ThreadStart, Int32)"
https://msdn.microsoft.com/en-us/library/5cykbwz4(VS.90).aspx
"Avoid using this constructor overload. The default stack size used by the Thread(ThreadStart) constructor overload is the recommended stack size for threads.
If a thread has memory problems, the most likely cause is programming error, such as infinite recursion."

Comments

  • Anonymous
    May 17, 2012
    Is it due to the number of connections made on the page?