3

我有一组项目(ADO.NET 实体框架),需要根据几个不同的条件返回一个子集作为搜索结果。不幸的是,标准以这样的方式重叠,以至于我不能只获取Where满足条件的集合(或删除Where不满足条件),因为这会遗漏或重复应该返回的有效项目。

我决定单独进行每项检查,并结合结果。我考虑过使用AddRange,但这会导致结果列表中出现重复(我的理解是它每次都会枚举集合 - 我在这里是正确/错误吗?)。我意识到Union不插入重复项,并将枚举推迟到必要时(再次,这种理解是否正确?)。

搜索写法如下:

IEnumerable<MyClass> Results = Enumerable.Empty<MyClass>();
IEnumerable<MyClass> Potential = db.MyClasses.Where(x => x.Y); //Precondition

int parsed_key;

//For each searchable value
foreach(var selected in SelectedValues1)
{
    IEnumerable<MyClass> matched = Potential.Where(x => x.Value1 == selected);
    Results = Results.Union(matched); //This is where the problem is
}

//Ellipsed....

foreach(var selected in SelectedValuesN) //Happens to be integer
{
    if(!int.TryParse(selected, out parsed_id))
        continue;
    IEnumerable<MyClass> matched = Potential.Where(x => x.ValueN == parsed_id);
    Results = Results.Union(matched); //This is where the problem is
}

然而,这似乎Results = Results.Union(matched)更像是Results = matched. 我已经完成了一些测试数据和测试搜索。搜索要求第一个字段为 -1、0、1 或 3 的结果。这应该返回 4 个结果(两个 0,一个 1 和一个 3)。循环的第一次迭代按预期工作,结果仍然为空。第二次迭代也按预期工作,结果包含两个项目。然而,在第三次迭代之后,Results 只包含一项。

我只是误解了它.Union的工作原理,还是这里发生了其他事情?

4

5 回答 5

8

由于延迟执行,当您最终使用 Results时,它是许多Where查询的联合,所有这些查询都基于 的最后一个值 selected

所以你有了

Results = Potential.Where(selected)
    .Union(Potential.Where(selected))
    .Union(potential.Where(selected))...

并且所有的selected值都是一样的。

您需要var currentSelected = selected在循环内部创建一个并将其传递给查询。这样每个值都selected将被单独捕获,您将不会遇到此问题。

于 2012-08-13T14:08:14.440 回答
1

你可以更简单地做到这一点:

Reuslts = SelectedValues.SelectMany(s => Potential.Where(x => x.Value == s));

(这可能会返回重复项)

或者

Results = Potential.Where(x => SelectedValues.Contains(x.Value));
于 2012-08-13T14:05:51.260 回答
1

正如其他人所指出的,您的 LINQ 表达式是一个闭包。这意味着您的变量selected在 foreach 循环的每次迭代中都被 LINQ 表达式捕获。在 foreach 的每次迭代中都使用相同的变量,因此它最终将具有最后一个值。为了解决这个问题,您需要在 foreach 循环中声明一个局部变量,如下所示:

//For each searchable value 
foreach(var selected in SelectedValues1) 
{
    var localSelected = selected;
    Results = Results.Union(Potential.Where(x => x.Value1 == localSelected));
}

仅使用要短得多.Contains()

Results = Results.Union(Potential.Where(x => SelectedValues1.Contains(x.Value1)));

由于您需要查询多个SelectedValues集合,因此您可以将它们全部放在自己的集合中并对其进行迭代,尽管您需要某种方式来匹配对象上的正确字段/属性。

您可以通过将所选值的列表存储在以字段/属性名称作为键的字典中来执行此操作。您将使用反射来查找正确的字段并执行检查。然后,您可以将代码缩短为以下内容:

// Store each of your searchable lists here
Dictionary<string, IEnumerable<MyClass>> DictionaryOfSelectedValues = ...;

Type t = typeof(MyType);
// For each list of searchable values
foreach(var selectedValues in DictionaryOfSelectedValues) // Returns KeyValuePair<TKey, TValue>
{
    // Try to get a property for this key
    PropertyInfo prop = t.GetProperty(selectedValues.Key);
    IEnumerable<MyClass> localSelected = selectedValues.Value;

    if( prop != null )
    {
        Results = Results.Union(Potential.Where(x =>
                localSelected.Contains(prop.GetValue(x, null))));
    }
    else // If it's not a property, check if the entry is for a field
    {
        FieldInfo field = t.GetField(selectedValues.Key);
        if( field != null )
        {
            Results = Results.Union(Potential.Where(x =>
                    localSelected.Contains(field.GetValue(x, null))));
        }
    }
}
于 2012-08-13T14:39:47.240 回答
0

不,您对 union 的使用是绝对正确的。唯一要记住的是它排除了基于相等运算符的重复项。你有样本数据吗?

于 2012-08-13T14:06:04.040 回答
0

好的,我认为您遇到了问题,因为Union使用了延迟执行。

如果你这样做会发生什么,

var unionResults = Results.Union(matched).ToList();
Results = unionResults; 
于 2012-08-13T14:11:22.803 回答