Почему мое предложение Linq Where дает больше результатов, чем меньше? - PullRequest
2 голосов
/ 06 мая 2009

У меня только что был самый странный опыт отладки за очень долгое время. Это немного стыдно признать, но это заставило меня поверить, что мой запрос Linq дает БОЛЬШЕ результатов при добавлении дополнительного предложения Where.

Я знаю, что это невозможно, поэтому я реорганизовал свою функцию-нарушитель, а также относящийся к ней модульный тест:

[Test]
public void LoadUserBySearchString()
{
    //Setup
    var AllUsers = new List<User>
                       {
                           new User
                               {
                                   FirstName = "Luke",
                                   LastName = "Skywalker",
                                   Email = "luke@jedinet.org"
                               },
                           new User
                               {
                                   FirstName = "Leia",
                                   LastName = "Skywalker",
                                   Email = "faeryprincess@winxmail.com"
                               }
                       };


    //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 и утверждал, что второй вызов функции фактически создает запрос linq с двумя предложениями where вместо одного. Таким образом, кажется, что дополнительное условие where увеличивает количество результатов.

Что еще более странно, функция LoadUserBySearchString прекрасно работает, когда я тестирую ее вручную (с реальными пользователями из базы данных). Это показывает только странное поведение при запуске модульного теста.

Полагаю, мне просто нужно немного поспать (или даже продлить отпуск). Если бы кто-нибудь мог помочь мне пролить свет на это, я мог бы прекратить сомневаться в своем здравомыслии и вернуться к работе.

Спасибо

Адриан

Редактировать (чтобы уточнить несколько ответов, я дошел до сих пор) : Я знаю, что это выглядит как предложение или, но, к сожалению, не все так просто. 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.

Ответы [ 2 ]

6 голосов
/ 06 мая 2009

Это милый маленький гоча.

Что происходит, так это то, что из-за анонимных методов и отложенного выполнения вы фактически не фильтруете «принцессу». Вместо этого вы создаете фильтр, который будет фильтровать содержимое переменной subString.

Но затем вы изменяете эту переменную и создаете другой фильтр, который снова использует ту же переменную.

По сути, это то, что вы выполните в краткой форме:

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

Итак, вы фактически фильтруете только последнее слово, которое существует в обоих, просто потому, что к тому времени, когда эти фильтры фактически применяются, остается только одно значение 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)
                               ));
}
1 голос
/ 06 мая 2009

Ваш алгоритм сводится к «выбору записей, которые соответствуют любому из слов в строке поиска».

Это из-за отложенного выполнения. Запрос фактически не выполняется, пока вы не вызовете .ToList (). Если вы переместите .ToList () внутри цикла, вы получите желаемое поведение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...