2009-05-06 14 views
2

Ich hatte gerade die seltsamste Debug-Erfahrung in einer sehr langen Zeit. Es ist ein bisschen peinlich zuzugeben, aber es führt mich zu der Annahme, dass meine Linq-Abfrage MEHR Ergebnisse erzeugt, wenn ich eine zusätzliche Where-Klausel hinzufüge.Warum produziert meine Linq Where-Klausel mehr Ergebnisse statt weniger?

Ich weiß es nicht möglich ist, so habe ich meine säumige Funktion plus der Unit-Test, um es in dieser Zugehörigkeit Refactoring:

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

ich die LoadUserBySearchString Funktion ausgetestet haben und behauptet, dass der zweite Aufruf der Funktion erzeugt tatsächlich eine linq-Abfrage mit zwei where-Klauseln anstelle von eins. So scheint es, dass die zusätzliche where-Klausel die Anzahl der Ergebnisse erhöht.

Was noch seltsamer ist, funktioniert die LoadUserBySearchString-Funktion gut, wenn ich es manuell testen (mit echten Benutzern aus der Datenbank). Es zeigt nur dieses seltsame Verhalten beim Ausführen des Komponententests.

Ich denke, ich brauche nur etwas Schlaf (oder sogar einen längeren Urlaub). Wenn irgendjemand mir helfen könnte, etwas Licht in diese Sache zu bringen, könnte ich aufhören, meine geistige Gesundheit in Frage zu stellen und wieder an die Arbeit gehen.

Danke,

Adrian

Edit (auf mehrere Antworten zu klären, ich so weit gehen): Ich weiß, es sieht aus wie es die oder Klausel, aber Unfortuantely ist es nicht so einfach. LoadUserBySearchString teilt die Suchzeichenfolge in mehrere Zeichenfolgen und fügt für jede von ihnen eine Where-Klausel an. "Skywalker" passt zu Luke und Leia, aber "Princess" passt nur zu Leia.

Dies ist die Linq-Abfrage für den Suchbegriff „Prinzessin“:

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

Und das ist die Linq-Klausel für den Suchtext „Prinzessin Skywalker“

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

Wie oben, nur mit einer zusätzlichen where-Klausel.

Antwort

6

Dies ist ein nettes kleines Gotcha.

Was ist passiert, dass Sie wegen anonymer Methoden und verzögerte Ausführung tatsächlich nicht auf "Prinzessin" filtern. Stattdessen erstellen Sie einen Filter, der nach dem Inhalt der Variablen subString filtern soll.

Aber Sie ändern dann diese Variable und erstellen einen anderen Filter, der wiederum die gleiche Variable verwendet.

Im Grunde ist das, was Sie ausführen, in Kurzform:

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

so, bist du eigentlich nur auf dem letzten Wort Filterung, die in beiden vorhanden ist, einfach deshalb, weil durch die Zeit, diese Filter tatsächlich angewendet, ist nur ein SubString-Wert übrig, der letzte.

Wenn Sie den Code ändern, so dass Sie die SubString Variablen innerhalb des Umfangs der Schleife erfassen, wird es funktionieren:

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

Vielen Dank! Du hast meinen Tag gemacht :-) –

+0

+1, die Verwendung einer lokalen Variable hilft normalerweise, Schließungsprobleme zu lösen. Weitere Informationen über die Verwendung von Verschlüssen in LINQ: http://diditwith.net/2007/09/25/LINQClosuresMayBeHazardousToYourHealth.aspx – Lucas

1

Ihr Algorithmus beläuft sich auf "Datensätze auswählen, die mit einem der Wörter in der Suchzeichenfolge übereinstimmen".

Dies liegt an der verzögerten Ausführung. Die Abfrage wird nicht tatsächlich ausgeführt, bis Sie die .ToList() aufrufen. Wenn Sie die .ToList() innerhalb der Schleife verschieben, erhalten Sie das gewünschte Verhalten.

+0

Sie falsch sind, lassevk Antwort sehen. – Samuel

+3

Möchten Sie erklären, welcher Teil meiner Antwort falsch ist? Ich bin in beiden Punkten richtig - es wird durch verzögerte Ausführung verursacht, und die Ausführung einer .ToList() innerhalb der Schleife jedes Mal geben würde die richtige Antwort. –

+0

Ihre erste Aussage ist falsch. Lesen Sie seinen Code und Sie werden sehen, warum. Und Ihr Vorschlag funktioniert nur, wenn er innerhalb jeder Schleife ToList(). AsQueryable() aufruft, da er IQueryable benötigt. Und was passiert, wenn das IQueryable langsam ist? Du hast seine Abfrage nun um ein paar Potenzen verlangsamt. – Samuel

Verwandte Themen