【问题标题】:Why does my Linq Where clause produce more results instead of less?为什么我的 Linq Where 子句产生的结果更多而不是更少?
【发布时间】:2009-05-06 16:25:26
【问题描述】:

我刚刚经历了很长一段时间以来最奇怪的调试体验。承认有点尴尬,但它让我相信我的 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 函数并断言对该函数的第二次调用实际上产生了一个带有两个 where 子句而不是一个的 linq 查询。因此,附加的 where 子句似乎增加了结果的数量。

更奇怪的是,LoadUserBySearchString 函数在我手动测试时效果很好(使用数据库中的真实用户)。它仅在运行单元测试时显示这种奇怪的行为。

我想我只是需要一些睡眠(甚至是延长假期)。如果有人可以帮我阐明这一点,我可以停止质疑我的理智并回去工作。

谢谢,

阿德里安

编辑(澄清我到目前为止的几个回复):我知道它看起来像是 or 子句,但不幸的是它并不是那么简单。 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 子句。

【问题讨论】:

    标签: c# linq linq-to-entities


    【解决方案1】:

    这是一个不错的小问题。

    发生的情况是,由于匿名方法和延迟执行,您实际上并没有过滤“公主”。相反,您正在构建一个过滤器,它将过滤 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,使用局部变量通常有助于解决闭包问题。进一步阅读关于在 LINQ 中使用闭包:diditwith.net/2007/09/25/…
    【解决方案2】:

    您的算法相当于“选择与搜索字符串中的任何单词匹配的记录”。

    这是因为延迟执行。在调用 .ToList() 之前,不会实际执行查询。如果你在循环内移动 .ToList(),你会得到你想要的行为。

    【讨论】:

    • 想解释一下我的答案的哪一部分是错误的?我在这两点上都是正确的 - 它是由延迟执行引起的,并且每次在循环内执行 .ToList() 都会给出正确的答案。
    • 您的第一句话是错误的。阅读他的代码,你就会明白为什么。你的建议只有在他在每个循环中调用 ToList().AsQueryable() 时才有效,因为他需要 IQueryable。如果 IQueryable 很慢,会发生什么?你现在已经让他的查询慢了几倍。
    • 虽然“选择与搜索字符串中的最后一个单词匹配的记录”会更准确,但 Joe 是第一个发现这是延迟执行问题的人。那谢谢啦! :-)
    猜你喜欢
    • 2011-12-24
    • 2012-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-08
    • 2019-02-04
    • 1970-01-01
    • 2020-05-05
    相关资源
    最近更新 更多