IEnumerable <t> .Union(IEnumerable <t>)覆盖内容而不是联合</t> </t>

时间:2012-08-13 14:02:55

标签: c# ienumerable

我有一组项目(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)。循环的第一次迭代按预期工作,结果仍为空。第二次迭代也按预期工作,结果包含两个项目。但是,在第三次迭代之后,结果只包含一个项目。

我是否误解了.Union的工作原理,或者其他地方有什么问题?

5 个答案:

答案 0 :(得分:8)

由于延迟执行,当您最终消费 Results时,它是许多Where个查询的并集,所有查询都基于最后一个价值 selected

所以你有

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

并且所有selected值都相同。

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

答案 1 :(得分:1)

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

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

(这可能会返回重复)

或者

Results = Potential.Where(x => SelectedValues.Contains(x.Value));

答案 2 :(得分:1)

正如其他人所指出的,你的LINQ表达式是closure。这意味着在foreach循环的每次迭代中,LINQ表达式都会捕获变量selected。在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集合,因此您可以将它们全部放在自己的集合中并对其进行迭代,尽管您需要某种方法来匹配对象上的正确字段/属性。

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

// 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))));
        }
    }
}

答案 3 :(得分:0)

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

答案 4 :(得分:0)

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

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

var unionResults = Results.Union(matched).ToList();
Results = unionResults;