2012-08-13 97 views
3

我有一个项目集合(ADO.NET实体框架),并需要返回一个子集作为搜索结果根据几个不同的标准。不幸的是,标准重叠的方式使得我不能仅仅采集Where符合标准(或者落下Where不符合标准),因为这会遗漏或重复应该返回的有效项目。IEnumerable <T> .Union(IEnumerable <T>)覆盖的内容而不是工会

我决定我会每个检查单独,并结合结果。我考虑使用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,a 1和a 3)。循环的第一次迭代按预期工作,结果仍为空。第二次迭代也按预期工作,结果包含两个项目。但是,在第三次迭代之后,结果只包含一个项目。

我刚刚误解了.Union是如何工作的,还是有其他事情在这里发生?

+2

可变名称应以小写字母开头,它使样本更易于阅读。 – Jodrell 2012-08-13 14:06:10

+0

我相信在实体类中实现'IEqualityComparer '会很有意思,因为联合操作使用此接口。 – 2012-08-13 14:07:40

+0

你可以发布你的测试数据吗? – 2012-08-13 14:08:05

回答

8

由于延迟执行的,到时候你最终消耗Results,这是很多Where联合查询所有这些都是基于selected最后一个值。

所以,你必须

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

和所有selected值相同。

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

+0

是的,“被选中”被捕获。另一种解决方案是该循环内的ToList()。 – 2012-08-13 14:10:48

+0

@亨克是的,我打算去“让结果成为一个列表”,但只是调用“ToList”就是真正需要的。 – Rawling 2012-08-13 14:12:43

+0

这工作!几个被搜索的字段是整数。对于那些我在循环之前创建了'int d_id'或类似的东西,然后'TryParsed'这个搜索词在循环内搜索。没有意识到延迟执行会使用搜索参数*的当前值,而不是我将它联合时的值。 – yoozer8 2012-08-13 14:16:11

1

你可以简单地做更多:

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

(这可能会返回重复)

或者

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

这将简化对每个可搜索值的搜索,但需要检查几个不同的值。我不确定问题中是否清楚(现在不重新阅读它),但对于不同的值,for循环会重复执行几次。编辑澄清... – yoozer8 2012-08-13 14:07:45

+0

@Jim:你可以用'||'子句做到这一点。 – SLaks 2012-08-13 14:39:56

0

不,你的工会的使用是正确的absoloutely。 唯一要记住的是它不包含基于相等运算符的重复项。你有样品数据吗?

0

好的,我认为你有一个问题,因为Union使用延迟执行。

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

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

正如其他人所指出的,你的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集合,您可以将它们全部放入自己的集合中,并迭代它,尽管您需要某种方法来匹配对象上的正确字段/属性。

您可以通过将所选值的列表以字段/属性的名称作为关键字存储在Dictionary中来执行此操作。你可以使用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)))); 
     } 
    } 
} 
相关问题