我有一组项目(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
的工作原理,或者其他地方有什么问题?
答案 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;