当我将它从IEnumerable更改为List时,为什么我的代码工作?

时间:2013-10-16 10:04:49

标签: c# linq

我正试图解决这个问题,而不仅仅是把它变成一般伏都教。

我执行EF查询并获取一些数据,我.ToList()就像这样:

IEnumerable<DatabaseMatch<CatName>> nameMatches = nameLogicMatcher.Match(myIQueryableOfCats).ToList();

有些猫在数据库中出现两次,因为它们有多个名称,但每只猫都有一个主要名称。因此,为了对此进行过滤,我将猫的所有ID都列在一个列表中:

List<int> catIds = nameMatches.Select(c => c.Match.CatId).ToList();

然后我遍历所有不同的ID,获取所有匹配的cat名称,并从列表中删除任何非主要名称的内容,如下所示:

foreach (int catId in catIds.Distinct())
{
    var allCatNameMatches = nameMatches.Where(c => c.Match.CatId == catId);

    var primaryMatch = allCatNameMatches.FirstOrDefault(c => c.Match.NameType == "Primary Name");

    nameMatches = nameMatches.Except(allCatNameMatches.Where(c => c != primaryMatch)); 
}

现在这个代码,当我第一次运行时,只是挂了。我觉得这很奇怪。我穿过它,它似乎工作,但经过10次迭代(它总共限制在100只猫)它开始减速,然后最终它是冰川,然后完全挂起。

我想也许是错误地做了一些密集的数据库工作,但是分析器显示没有执行SQL,除了检索cat名称的初始列表。

我决定将其从IEnumerable的nameMatches更改为List,并将相应的.ToList()放在最后一行。在我这样做之后,它立即完美地工作。

我想问的问题是,为什么?

2 个答案:

答案 0 :(得分:3)

如果没有ToList(),您正在nameMatches中建立一个IEnumerable的嵌套链,等待延迟执行。这可能不是那么糟糕,除了你在执行链的每次迭代时也调用FirstOrDefault。因此,在迭代次数 n 上,您正在执行循环 n-1 次中包含的过滤器操作。如果您有1000只不同的猫,Linq链将被执行 1000 + 99 + ... + 1 次。 (我认为你有 O(n³)的东西!)

道德是,如果你想使用延迟执行,请确保你只执行一次链。

答案 1 :(得分:0)

让我们简化一下你的代码:

foreach (int catId in catIds.Distinct())
{
    var allCatNameMatches = nameMatches.Where(c => c.Match.CatId == catId);
    var primaryMatch = null;
    nameMatches = nameMatches.Except(allCatNameMatches.Where(c => c != primaryMatch)); 
}

还有一点:

foreach (int catId in catIds.Distinct())
{
    nameMatches = nameMatches.Where(c => c.Match.CatId == catId);
    var primaryMatch = null;
    nameMatches = nameMatches.Except(nameMatches.Where(c => c != primaryMatch)); 
}

在后者中很明显,由于延迟执行,foreach身体的每次传递都会延长WhereExcept的链。然后记住var primaryMatch = allCatNameMatches.FirstOrDefault。它不是延迟执行的,因此在foreach的每次迭代中它都应该执行所有链。因此它会挂起。