动态构建的LINQ查询根据其构建方式提供不一致的结果

时间:2012-12-07 17:51:58

标签: c# .net linq

我正在尝试构建一个通用查询构建器,用于接受用户输入的搜索词。为了得到我需要的where子句,我必须解析术语并独立地确定哪个字段需要包含在该子句中。

请注意,此示例大大简化以说明这一点。我知道在这种特殊情况下,整个结果可以表示为单个Where()语句。这段代码在我的问题空间中工作(其中一个Where()语句不会),所以答案必须解决实际发生的问题,而不是如何简化它。

我从一个术语列表开始(这里表示为一个字符串[],但最终将是一个更复杂类型的IList,它有助于指导查询构建器):

    string[] terms = new string[] {
        "hope",
        "bob",
    };

这个第一个例子(下面)给出了正确的结果集(任何一个员工在三个搜索字段中都有'bob'的记录,并且在三个字段匹配中都有'希望')。这确实证明了在链接Where()子句时使用代码构建了正确的查询:

    var query0 = Sites.Where(s => s.SiteId < 200);
    query0 = query0.Where(s =>
        s.Employee.FirstName.Contains(terms[0]) ||
        s.Employee.LastName.Contains(terms[0]) ||
        s.Employee.Username.Contains(terms[0]));
    query0 = query0.Where(s =>
        s.Employee.FirstName.Contains(terms[1]) ||
        s.Employee.LastName.Contains(terms[1]) ||
        s.Employee.Username.Contains(terms[1]));
    query0.Dump();

(请注意,我不能使用这种直接方法,因为我不知道会有多少条款,其中一些将在Employee上,而其他一些将在'Sites'的其他领域,所以在编译时时间我必须能够独特地迭代和处理每个术语。)

下一个示例(下面)是我想要做的事情,但它不尊重第一个术语,只匹配最后一个术语;无论“希望”是否出现在任何字段中,都包含任何字段中包含“bob”的记录:

    var query1 = Sites.Where(s => s.SiteId < 200);
    foreach (string term in terms)
    {
        query1 = query1.Where(s =>
            s.Employee.FirstName.Contains(term) ||
            s.Employee.LastName.Contains(term) ||
            s.Employee.Username.Contains(term));
    }
    query1.Dump();

这个最后的例子(下面)在到达Dump()指令时给出了'index out of bounds'错误:

    var query2 = Sites.Where(s => s.SiteId < 200);
    for (int i = 0; i < terms.Length; ++i)
    {
        query2 = query2.Where(s =>
            s.Employee.FirstName.Contains(terms[i]) ||
            s.Employee.LastName.Contains(terms[i]) ||
            s.Employee.Username.Contains(terms[i]));
    }
    query2.Dump();

我认为query2是最有说服力的例子。这几乎就像LINQ在构建完整个查询后尝试将变量绑定到SQL参数一样,并且它试图使用i == 2(这将是循环退出时i的值) all 绑定。这也与我在query1中看到的结果一致。

有谁知道绑定是如何工作的以及如何构建我的查询?

1 个答案:

答案 0 :(得分:2)

最后一个示例是演示您捕获变量,而不是。当您执行查询时,i的值为terms.Length,因此您遇到了问题。最小的变化是使用:

for (int i = 0; i < terms.Length; ++i)
{
    int copy = i;
    query2 = query2.Where(s =>
        s.Employee.FirstName.Contains(terms[copy]) ||
        s.Employee.LastName.Contains(terms[copy]) ||
        s.Employee.Username.Contains(terms[copy]));
}

现在循环的每次迭代都有一个名为copy的单独变量,它不会改变 - 所以你很好。

现在,使用foreach循环会更清晰,但现在取决于您是否使用C#5。在C#5中,您可以使用:

// Only works in C# 5
foreach (string term in terms)
{
    query2 = query2.Where(s =>
        s.Employee.FirstName.Contains(term) ||
        s.Employee.LastName.Contains(term) ||
        s.Employee.Username.Contains(term));
}

但是在C#3或C#4中,这是行不通的,因为你在整个循环中都有一个term变量,而你必须使用它:

// Works in C# 3+
foreach (string term in terms)
{
    string copy = term;
    query2 = query2.Where(s =>
        s.Employee.FirstName.Contains(copy) ||
        s.Employee.LastName.Contains(copy) ||
        s.Employee.Username.Contains(copy));
}