2009-05-06 9 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(); 
} 

をその関数への2回目の呼び出し実際にはwhere句の代わりに2つの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>} 

そして、これは単に、上記のように

+  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の句であります1つの追加のwhere節があります。

答えて

6

これはちょっとした問題です。

何が起こっているかは、匿名メソッドと遅延実行のために、実際には「プリンセス」をフィルタリングしていないということです。代わりに、subString変数の内容をフィルタリングするフィルタを作成しています。

しかし、この変数を変更して、同じ変数を再び使用する別のフィルタを作成します。

基本的に、これはあなたが短い形式で、実行されるものである。

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

ので、あなたが実際に唯一の時間でこれらのフィルタがあるという理由だけで、両方に存在する最後の単語、上でフィルタリングしています実際に適用された場合、最後に残るSubString値は1つだけです。

あなたはループの範囲内の部分文字列変数をキャプチャするように、コードを変更すると、それがうまくいく:

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()を呼び出す場合にのみ機能し、IQueryable が必要なので、各ループ内のAsQueryable()を使用します。 IQueryableが遅い場合はどうなりますか?あなたは今、彼のクエリーに2つの力をゆっくりとさせました。 – Samuel

関連する問題