2013-02-21 2 views
3

일부 매개 변수를 기반으로 문자열 목록이 주어진 두 날짜 사이에 '트랜잭션'을 찾는 방법이 있습니다. 목록이 1000보다 크면 목록에서 반복을 시도하는 스택 오버플로 예외가 발생합니다.LINQ to SQL의 스택 오버플로 예외

은 여기 비주얼 스튜디오가 처리 할 수있는 지점을지나갑니다 ... 여기 내 코드

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

이다 스택 추적입니다.

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  

내 질문에 내가 스택 오버플로 예외에 대해 걱정할 필요가 없도록이 쿼리를 처리 할 수있는 방법이 무엇입니까?

+0

어떤 Linq 버전을 사용하고 있습니까? 이 문제를 일으킬 수있는 버그 4.0 이전 버전이있었습니다 : http://damieng.com/blog/2009/06/01/linq-to-sql-changes-in-net-40 * "이제는 Contains가 self- IQueryable을 참조하고 스택 오버플로가 발생하지 않습니다. "* –

+0

@MatthewWatson .NET Framework 4.0을 사용하고 있습니다. 감사합니다. :) –

+0

스택 오버플로를 처리하기위한 일반적인 제안 : 디버거의 호출 스택을보고 어떤 메소드가 포함되어 있는지 확인하십시오. –

답변

0

일부 시행 착오 끝에 몇 가지 대안을 살펴본 결과, 나는 꽤 잘 해결되는 해결책을 생각해 냈습니다.

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

내가 거기에 상상이 where 절에 사용되는 목록과 제한의 일종이다, 이것은 내가 사용자 코드를 제한하고 만을 얻을 수있는 간단한 필터를하고 있어요으로 더 나은 솔루션 었죠 관련 코드 목록에있는

+1

해결 방법 찾기에 좋은 작업입니다. Linq에서 게으름과 관련하여 .NET 팀이 수행해야하는 작업 중 일부를 강조했다고 생각합니다. – pwnyexpress

2

큰 컬렉션을 반복하면서 스택을 부는 것처럼 보일뿐 아니라 동시에 두 개의 크고 기본적으로 동일한 컬렉션을 생성하는 목록에 해당 개체를 동시에 추가하는 것처럼 보입니다. 대신 모든 IEnumerable을 허용하는 목록에 대해 AddRange를 사용하십시오.

또는 빈 목록을 인스턴스화하지 않고

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

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

또는 게으름을 유지하기는 ...

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>(); 
    } 
} 
+0

이것은 컴파일되지 않습니다. 'void'를 'List '으로 변환 할 수 없습니다. –

+0

더 좋은 컴파일 방법을 보려면 편집을 참조하십시오. 첫 번째 예제에서는 내부 linq 쿼리의 끝에서 .AsEnumerable() 확장 메서드가 필요할 수 있습니다. – pwnyexpress

+0

두 번째 예제를 시도해 보았지만 컴파일은되지만 여전히 StackOverflowException이 발생합니다. –

0

현재 두 개의 큰 문제가 (게으른를 사용하는 편집) - 각 고유 ID 키에 대해 단일 속성으로 메모리에 새 객체를 생성합니다. 또한 쓸모없는 지역 목록이 있습니다. 여기서 모든 객체를 복사합니다. 목록의 용량이 채워질 때마다 새로운 내부 배열이 만들어지고 모든 객체가 복사됩니다.

IEnumerable에서 스트리밍 처리를 사용할 수 있습니다. 이 경우 메모리에 모든 데이터를 보유 할 필요가 없습니다 : 당신이 목록을해야하는 경우이 쿼리에 ToList()을 적용 할 수 있습니다

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

. 그러나 익명 개체를 만들고 로컬 목록에 복사 할 필요는 없습니다.