2009-05-06 63 views
2

我在很長一段時間裏只有最奇怪的調試體驗。承認這有點尷尬,但它讓我相信當添加一個額外的Where子句時,我的Linq查詢會產生更多結果。爲什麼我的Linq Where子句產生更多結果而不是更少?

我知道這是不可能的,所以我重構我得罪功能加上屬於它進入這個單元測試:

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

我已經調試的LoadUserBySearchString功能,並斷言,第二次調用該函數實際上用兩個where子句而不是一個生成linq查詢。所以看起來附加的where子句增加了結果的數量。

更奇怪的是,LoadUserBySearchString函數在我手動測試(真正的數據庫用戶)時效果很好。它僅在運行單元測試時顯示這種奇怪的行爲。

我想我只是需要一些睡眠(或甚至延長假期)。如果有人能幫我解釋一下,我可以不再質疑我的理智並回去工作。

感謝,

阿德里安

編輯(澄清的幾個回答,我走這麼遠):我知道這看起來是或條款,但unfortuantely它不是那麼簡單。 LoadUserBySearchString將搜索字符串分割成多個字符串,併爲每個字符串附加一個Where子句。 「天行者」匹配盧克和萊亞,但「公主」只匹配萊亞。

這是搜索字符串「公主」 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>} 

這是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))).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>} 

同上,只是另加一條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

非常感謝!你做了我的一天:-) – 2009-05-06 16:48:19

+0

+1,使用局部變量通常有助於解決關閉問題。進一步閱讀關於在LINQ中使用閉包的方法:http://diditwith.net/2007/09/25/LINQClosuresMayBeHazardousToYourHealth.aspx – Lucas 2009-05-06 17:52:50

1

您的算法相當於「選擇與搜索字符串中任何單詞匹配的記錄」。

這是因爲推遲執行。在您調用.ToList()之前,查詢實際上不會執行。如果你在循環中移動.ToList(),你會得到你想要的行爲。

+0

你錯了,請參閱lassevk的答案。 – Samuel 2009-05-06 16:51:26

+3

小心解釋我答案的哪一部分是錯誤的?我對這兩點都是正確的 - 這是由延遲執行引起的,並且每次在循環內部執行.ToList()都會給出正確的答案。 – 2009-05-06 17:06:06

+0

你的第一個說法是錯誤的。閱讀他的代碼,你會看到爲什麼。而且你的建議只有在他需要IQueryable 時才能在每個循環內調用ToList().AsQueryable()。如果IQueryable很慢,會發生什麼?你現在已經讓他的查詢功能變慢了。 – Samuel 2009-05-06 17:08:44

相關問題