【问题标题】:IEnumerable<T>.Union(IEnumerable<T>) overwrites contents instead of unioningIEnumerable<T>.Union(IEnumerable<T>) 覆盖内容而不是联合
【发布时间】:2012-08-13 14:02:54
【问题描述】:

我有一组项目(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 的工作原理,还是这里发生了其他事情?

【问题讨论】:

  • 变量名应该以小写字母开头,这样更容易阅读示例。
  • 我相信在你的实体类中实现IEqualityComparer&lt;T&gt; 会很有趣,因为联合操作使用了这个接口。
  • 你能发布你的测试数据吗?

标签: c# ienumerable


【解决方案1】:

由于延迟执行,当你最终消费Results时,它是许多Where查询的联合所有这些都基于最后一个值强>selected.

所以你有

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

所有selected 的值都是一样的。

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

【讨论】:

  • 是的,selected 已被捕获。另一种解决方案是该循环内的 ToList()。
  • @Henk 是的,我本来打算“将结果列在列表中”,但真正需要的是调用 ToList
  • 我认为他没有使用 .ToList 因为他想推迟执行。
  • @Servy 啊,很公平。很高兴知道他们正在“修复”它:)
  • @Rawling 好吧,这不是错误,这只是一个常见错误。我会让 Eric Lippert explain 加入他的 own words
【解决方案2】:

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

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

(这可能会返回重复)

或者

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

【讨论】:

  • 这将简化每个可搜索值的搜索,但需要检查几个不同的值。我不确定这个问题是否清楚(现在我重新阅读它时看起来并不明显),但是对于不同的值,for循环重复了几次。编辑澄清...
  • @Jim:您可以使用|| 子句来做到这一点。
【解决方案3】:

正如其他人所指出的,您的 LINQ 表达式是 closure。这意味着您的变量 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))));
        }
    }
}

【讨论】:

    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多