2009-05-06 5 views
2

방금 ​​아주 이상한 디버그 경험을했습니다. 그것은 인정하기에 다소 당황 스럽지만, Linq 쿼리가 추가 Where 절을 추가 할 때 더 많은 결과를 산출한다고 믿게합니다.왜 Linq Where 절은 적은 양이 아닌 많은 결과를 산출합니까?

나는 그것이 가능하지 알고, 그래서 난 내 기분을 상하게하는 기능을 더한로에 속하는 단위 테스트 리팩토링했습니다 나는 LoadUserBySearchString 기능을 디버깅하고 주장한다

[Test] 
public void LoadUserBySearchString() 
{ 
    //Setup 
    var AllUsers = new List<User> 
         { 
          new User 
           { 
            FirstName = "Luke", 
            LastName = "Skywalker", 
            Email = "[email protected]" 
           }, 
          new User 
           { 
            FirstName = "Leia", 
            LastName = "Skywalker", 
            Email = "[email protected]" 
           } 
         }; 


    //Execution 
    List<User> SearchResults = LoadUserBySearchString("princess", AllUsers.AsQueryable()); 
    List<User> SearchResults2 = LoadUserBySearchString("princess Skywalker", AllUsers.AsQueryable()); 

    //Assertion 
    Assert.AreEqual(1, SearchResults.Count); //test passed! 
    Assert.AreEqual(1, SearchResults2.Count); //test failed! got 2 instead of 1 User??? 
} 


//search CustID, fname, lname, email for substring(s) 
public List<User> LoadUserBySearchString(string SearchString, IQueryable<User> AllUsers) 
{ 
    IQueryable<User> Result = AllUsers; 
    //split into substrings and apply each substring as additional search criterium 
    foreach (string SubString in Regex.Split(SearchString, " ")) 
    {    
     int SubStringAsInteger = -1; 
     if (SubString.IsInteger()) 
     { 
      SubStringAsInteger = Convert.ToInt32(SubString); 
     } 

     if (SubString != null && SubString.Length > 0) 
     { 
      Result = Result.Where(c => (c.FirstName.Contains(SubString) 
             || c.LastName.Contains(SubString) 
             || c.Email.Contains(SubString) 
             || (c.ID == SubStringAsInteger) 
             )); 
     } 
    } 
    return Result.ToList(); 
} 

을 그 함수의 두 번째 호출 실제로 where 대신 하나의 절이있는 linq 쿼리를 생성합니다. 그래서 추가 where 절이 결과의 양을 늘리는 것 같습니다.

무엇이 더 이상한가요? LoadUserBySearchString 함수는 손으로 (데이터베이스의 실제 사용자와 함께) 테스트 할 때 훌륭하게 작동합니다. 단위 테스트를 실행할 때 이상한 행동 만 보여줍니다.

나는 약간의 수면 (또는 심지어 연장 된 휴가)이 필요하다고 생각한다. 누군가가이 문제에 관해 어떤 생각을 밝히도록 도와 줄 수 있다면, 나는 내 정신을 묻지 않고 일하러 돌아갈 수있다.

감사합니다,

아드리안

편집 (내가 지금까지 이동 여러 가지 응답에 명확히하기 위해) : 나는 그것이 나 절처럼 보이는 알고 있지만 unfortuantely는 그렇게 간단하지 않다. LoadUserBySearchString은 검색 문자열을 여러 문자열로 분할하고 각각에 대해 Where 절을 첨부합니다. "Skywalker"는 루크와 레이아와 일치하지만 "공주"는 레이아와 만 일치합니다.

이 "공주"검색 문자열에 대한 LINQ 쿼리입니다 :

+  Result {System.Collections.Generic.List`1[TestProject.Models.User].Where(c => (((c.FirstName.Contains(value(TestProject.Controllers.SearchController+<>c__DisplayClass1).SubString) || c.LastName.Contains(value(TestProject.Controllers.SearchController+<>c__DisplayClass1).SubString)) || c.Email.Contains(value(TestProject.Controllers.SearchController+<>c__DisplayClass1).SubString)) || (c.ID = value(TestProject.Controllers.SearchController+<>c__DisplayClass3).SubStringAsInteger)))} System.Linq.IQueryable<TestProject.Models.User> {System.Linq.EnumerableQuery<TestProject.Models.User>} 

그리고 이것은 단지, 상기와

+  Result {System.Collections.Generic.List`1[TestProject.Models.User].Where(c => (((c.FirstName.Contains(value(TestProject.Controllers.SearchController+<>c__DisplayClass1).SubString) || c.LastName.Contains(value(TestProject.Controllers.SearchController+<>c__DisplayClass1).SubString)) || c.Email.Contains(value(TestProject.Controllers.SearchController+<>c__DisplayClass1).SubString)) || (c.ID = value(TestProject.Controllers.SearchController+<>c__DisplayClass3).SubStringAsInteger))).Where(c => (((c.FirstName.Contains(value(TestProject.Controllers.SearchController+<>c__DisplayClass1).SubString) || c.LastName.Contains(value(TestProject.Controllers.SearchController+<>c__DisplayClass1).SubString)) || c.Email.Contains(value(TestProject.Controllers.SearchController+<>c__DisplayClass1).SubString)) || (c.ID = value(TestProject.Controllers.SearchController+<>c__DisplayClass3).SubStringAsInteger)))} System.Linq.IQueryable<TestProject.Models.User> {System.Linq.EnumerableQuery<TestProject.Models.User>} 

같은 "공주 스카이 워커"검색 문자열에 대한 Linq에 절입니다 하나의 추가적인 where 절이 있습니다.

답변

6

이것은 아주 좋은 생각입니다.

익명 메소드 및 지연 실행으로 인해 실제로 "공주"를 필터링하지 않습니다. 대신 subString 변수의 내용을 필터링하는 필터를 작성하고 있습니다.

그러나이 변수를 변경하고 같은 변수를 다시 사용하는 다른 필터를 빌드하십시오.그래서

Where(...contains(SubString)).Where(...contains(SubString)) 

, 당신은 실제로 마지막 단어를 필터링하고, 단순히 때문에 이러한 필터는 시간에 의해, 모두에 존재합니다

기본적으로, 이것은 당신이 짧은 형태로 실행됩니다 것입니다 실제로 적용되면 마지막 하나의 SubString 값만 남습니다. 당신은 루프의 범위 내에서 문자열 변수를 캡처 할 수 있도록 코드를 변경하는 경우

, 그것은 작동합니다 :

if (SubString != null && SubString.Length > 0) 
{ 
    String captured = SubString; 
    Int32 capturedId = SubStringAsInteger; 
    Result = Result.Where(c => (c.FirstName.Contains(captured) 
           || c.LastName.Contains(captured) 
           || c.Email.Contains(captured) 
           || (c.ID == capturedId) 
           )); 
} 
+0

고마워요! 내 하루를 만들었습니다 :-) –

+0

+1, 지역 변수를 사용하면 보통 클로저 문제를 푸는데 도움이됩니다. LINQ에서 클로저 사용에 대한 추가 정보 : http://diditwith.net/2007/09/25/LINQClosuresMayBeHazardousToYourHealth.aspx – Lucas

1

귀하의 알고리즘은 "검색 문자열에있는 단어와 일치하는 레코드를 선택하십시오"입니다.

지연된 실행 때문입니다. .ToList()를 호출 할 때까지 실제로 쿼리가 수행되지 않습니다. 루프 내에서 .ToList()를 이동하면 원하는 동작을 얻게됩니다.

+0

당신은 잘못, lassevk의 답변을 참조하십시오. – Samuel

+3

내 대답의 어느 부분이 잘못 되었는가를주의하십시오. 나는 두 지점에서 모두 정확하다. 지연된 실행으로 인해 발생하며 매번 올바른 답을 줄 루프 내에서 .ToList()를 수행한다. –

+0

첫 문장이 잘못되었습니다. 코드를 읽으면 그 이유를 알 수 있습니다. 그리고 당신의 제안은 ToList()를 호출 할 때만 작동합니다. AsQueryable()은 IQueryable 을 필요로하기 때문에 각 루프 내부에서 AsQueryable()을 호출합니다. 그리고 IQueryable이 느린 경우 어떻게됩니까? 이제 그의 질의에 몇 가지 힘을 느리게했습니다. – Samuel

관련 문제