5

I have a method that based on some parameters finds 'Transactions' between two dates given a list of strings. When the list is > 1000, I get a stack overflow exception trying to iterate on the list.

Here is my code

public List<string> CodesWithTransactionsBetweenDates(DateTime startInclusive, DateTime endExclusive, List<string> associatedCodes, int marketId)
    {
        List<string> codesWithTransactionsInPeriod = new List<string>();
        using (var context = new MarketPlaceEntities())
        {
            var transactionList = (from transactions in context.Transactions
                                   where
                                    associatedCodes.Contains(transactions.User.Code) &&
                                    transactions.MarketId == marketId &&
                                    transactions.Date >= startInclusive &&
                                    transactions.Date < endExclusive
                                   group transactions by transactions.User.Code into uniqueIds
                                   select new { UserCode = uniqueIds.Key });
            foreach (var transaction in transactionList)
            {
                codesWithTransactionsInPeriod.Add(transaction.UserCode);
            }
            return codesWithTransactionsInPeriod;
        }
    }

Here is the stack trace... it goes past the point which visual studio can handle.

System.Data.Entity.dll!System.Data.Query.InternalTrees.BasicOpVisitor.VisitChildren(System.Data.Query.InternalTrees.Node n) + 0x3 bytes 
System.Data.Entity.dll!System.Data.Query.PlanCompiler.GroupAggregateRefComputingVisitor.VisitDefault(System.Data.Query.InternalTrees.Node n) + 0x2b bytes   
System.Data.Entity.dll!System.Data.Query.InternalTrees.BasicOpVisitor.VisitRelOpDefault(System.Data.Query.InternalTrees.RelOp op, System.Data.Query.InternalTrees.Node n) + 0xe bytes   
System.Data.Entity.dll!System.Data.Query.InternalTrees.BasicOpVisitor.VisitApplyOp(System.Data.Query.InternalTrees.ApplyBaseOp op, System.Data.Query.InternalTrees.Node n) + 0xe bytes  
System.Data.Entity.dll!System.Data.Query.InternalTrees.BasicOpVisitor.Visit(System.Data.Query.InternalTrees.OuterApplyOp op, System.Data.Query.InternalTrees.Node n) + 0xe bytes    
System.Data.Entity.dll!System.Data.Query.InternalTrees.OuterApplyOp.Accept(System.Data.Query.InternalTrees.BasicOpVisitor v, System.Data.Query.InternalTrees.Node n) + 0x10 bytes   
System.Data.Entity.dll!System.Data.Query.InternalTrees.BasicOpVisitor.VisitNode(System.Data.Query.InternalTrees.Node n) + 0x14 bytes    
System.Data.Entity.dll!System.Data.Query.InternalTrees.BasicOpVisitor.VisitChildren(System.Data.Query.InternalTrees.Node n) + 0x60 bytes    

My question is what is a way I can handle this query so that I do not have to worry about a stack overflow exception?

4
  • What version of Linq are you using? There was a bug pre-4.0 that could cause this to happen: damieng.com/blog/2009/06/01/linq-to-sql-changes-in-net-40 "Contains now detects self-referencing IQueryable and doesn’t cause a stack overflow" Commented Feb 21, 2013 at 20:30
  • @MatthewWatson I am using .NET Framework 4.0 Thanks though :) Commented Feb 21, 2013 at 20:42
  • General suggestion for dealing with stack overflows: look at the call stack in a debugger to see what methods were involved. Commented Feb 21, 2013 at 21:20
  • The Stack overflow happens everytime in the ToList call... Is there some limitation to passing a list to ms sql ? Commented Feb 21, 2013 at 21:24

3 Answers 3

2

It seems like you're blowing the stack by iterating over the large collection, but also simultaneously add those objects to the list which results in two large, but basically identical collections. Instead just use AddRange for the list which accepts any IEnumerable.

List<string> codesWithTransactionsInPeriod = new List<string>();
using (var context = new MarketPlaceEntities())
    {
        return codesWithTransactionsInPeriod.AddRange((from transactions in context.Transactions
                               where
                                associatedCodes.Contains(transactions.User.Code) &&
                                transactions.MarketId == marketId &&
                                transactions.Date >= startInclusive &&
                                transactions.Date < endExclusive
                               group transactions by transactions.User.Code into uniqueIds
                               select uniqueIds.Key));
    }

or without instantiating an empty list...

using (var context = new MarketPlaceEntities())
    {
        return (from transactions in context.Transactions
                               where
                                associatedCodes.Contains(transactions.User.Code) &&
                                transactions.MarketId == marketId &&
                                transactions.Date >= startInclusive &&
                                transactions.Date < endExclusive
                               group transactions by transactions.User.Code into uniqueIds
                               select uniqueIds.Key).ToList<string>();
    }

or to preserve laziness... (Edited to use Lazy)

public Lazy<List<string>> LazyCodesWithTransactionsBetweenDates((DateTime startInclusive, DateTime endExclusive, List<string> associatedCodes, int marketId)
{
    return new Lazy<List<string>>(CodesWithTransactionsBetweenDates(startInclusive, endExclusive, associatedCodes, marketId));
}

private List<string> CodesWithTransactionsBetweenDates(DateTime startInclusive, DateTime endExclusive, List<string> associatedCodes, int marketId)
{
    using (var context = new MarketPlaceEntities())
    {
        return (from transactions in context.Transactions
                           where
                            associatedCodes.Contains(transactions.User.Code) &&
                            transactions.MarketId == marketId &&
                            transactions.Date >= startInclusive &&
                            transactions.Date < endExclusive
                           group transactions by transactions.User.Code into uniqueIds
                           select uniqueIds.Key).ToList<string>();
    }
}
Sign up to request clarification or add additional context in comments.

11 Comments

This doesn't compile, I get cannot convert type 'void' to 'List<string>'
See my edit for a better method that should compile. the first example may require the .AsEnumerable() extension method at the end of the inner linq query.
I did just try the second example and it does compile, but I still receive the StackOverflowException.
I think the problem you're facing here is really calling ToList(). Linq uses lazy evaluation and doesn't actually load objects into a collection until iterating over it, which is what ToList() does. I would suggest changing the method to return an IQueryable. See my 3rd edit...
The "preserve laziness" last suggestion is inappropriate because context will be disposed before the expression is evaluated.
|
1

You have two big problems here - for each unique id key you are creating new object in memory with single property. Also you have useless local list, where you copying all those objects. Each time when list's capacity filled, new inner array is created and all objects copied there.

You can use streaming processing with IEnumerable. In this case you don't need to hold all data in memory:

public IEnumerable<string> CodesWithTransactionsBetweenDates(
         DateTime startInclusive, DateTime endExclusive, 
         List<string> associatedCodes, int marketId)
{
    // do not use local list

    using (var context = new MarketPlaceEntities())
    {
        return from transactions in context.Transactions
                where associatedCodes.Contains(transactions.User.Code) &&
                      transactions.MarketId == marketId &&
                      transactions.Date >= startInclusive &&
                      transactions.Date < endExclusive
                      group transactions by transactions.User.Code into uniqueIds
                      select uniqueIds.Key; // do not create anonymous object
    }
}

If you need list, you can apply ToList() on this query. But you definitely don't need create anonymous objects and copy them to local list.

Comments

0

Okay, after some trial and error and looking at some alternatives, I came up with a solution that seems to work out quite well.

public List<string> CodesWithTransactionsBetweenDates(DateTime startInclusive, DateTime endExclusive, List<string> associatedCodes, int marketId)
{
    using (var context = new MarketPlaceEntities())
    {
        var list = (from transactions in context.Transactions                            
                where
                    transactions.MarketId == marketId &&
                    transactions.Date >= startInclusive &&
                    transactions.Date < endExclusive                            
                select transactions.User.Code).Distinct().ToList<string>();

        return list.Where(c => associatedCodes.Contains(c)).ToList();
    }            
}

I imagine there is some sort of limitation with a list being used in the where clause, this ended up being a better solution as I'm limiting the User codes and then doing a simple filter to get only those that are in the associated codes list.

1 Comment

Nice work on finding a work-around. I think you've highlighted a some of the things that the .NET team has to work on in regards to laziness in linq.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.