C#中的Linq to Objects - FirstOrDefault性能

5
我们正在尝试优化一些方法。我们使用Redgate的性能分析器来查找一些性能泄漏。
我们的工具在几个方法中使用对象的Linq。但是我们已经注意到,在包含+/-1000个对象的集合上,FirstOrDefault需要很长时间。
性能分析器也会警告查询非常缓慢。我已经添加了带有分析器结果的图片。
无法将集合添加到数据库然后查询数据库。
有什么推荐吗?
谢谢!
private SaldoPrivatiefKlantVerdeelsleutel GetParentSaldoPrivatiefKlantVerdeelsleutel(SaldoPrivatiefKlantVerdeelsleutel saldoPrivatiefKlantVerdeelsleutel, SaldoGebouwRekeningBoeking boeking, int privatiefKlant)
{
    SaldoPrivatiefKlantVerdeelsleutel parentSaldoPrivatiefKlantVerdeelsleutel = null;

    if (saldoPrivatiefKlantVerdeelsleutel != null)
    {
        try
        {
            parentSaldoPrivatiefKlantVerdeelsleutel = saldoPrivatiefKlantVerdeelsleutel.AfrekenPeriode.SaldoPrivatiefKlantVerdeelsleutelCollection
                .FirstOrDefault(s => (boeking == null || (s.SaldoVerdeelsleutel != null &&
                (s.SaldoVerdeelsleutel.GebouwVerdeelSleutel.ID == boeking.SaldoGebouwRekeningVerdeling.SaldoGebouwRekening.SaldoVerdeelsleutel.GebouwVerdeelSleutel.ID)))
                && s.PrivatiefKlant.ID == privatiefKlant);
        }
        catch (Exception ex)
        { }
    }

    return parentSaldoPrivatiefKlantVerdeelsleutel;
}

图像 : 个人资料报告


我的第一个想法是将 boeking == null 检查和 SaldoGebouwRekeningVerdeling.SaldoGebouwRekening.SaldoVerdeelsleutel.GebouwVerdeelSleutel.ID 值移动到循环之外。没有理由为集合中的每个元素重新评估它。 - Chris Sinclair
1
当您从表达式中读取时,查询将被执行。在这种情况下,FirstOrDefault将执行整个表达式树,因此它不是导致缓慢的FirstOrDefault。 - Jeroen van Langen
2
必要的提示:一个空的 catch{} 非常有害,应该将其移除。 - H H
1
现在我要问你一个问题...那些对象是通过NHibernate或Entity Framework加载的,对吗?你确定它们不是部分地惰性加载的吗?就像s.SaldoVerdeelsleutel.GebouwVerdeelSleutel.ID这样?因为它可能是一个经典的SELECT N+1反模式 - xanatos
4个回答

7
您应该能通过将其重写为以下内容来加快速度
saldoPrivatiefKlantVerdeelsleutel.AfrekenPeriode.SaldoPrivatiefKlantVerdeelsleutelCollection
            .Where(s => (boeking == null || (s.SaldoVerdeelsleutel != null &&
                (s.SaldoVerdeelsleutel.GebouwVerdeelSleutel.ID == boeking.SaldoGebouwRekeningVerdeling.SaldoGebouwRekening.SaldoVerdeelsleutel.GebouwVerdeelSleutel.ID))) && s.PrivatiefKlant.ID == privatiefKlant)
            .FirstOrDefault()

请参见为什么使用LINQ .Where(predicate).First()比使用.First(predicate)更快?,了解其原因。


2

FirstOrDefault 是对源集合执行标准线性搜索,并返回与谓词匹配的第一个元素。由于它是 O(n),因此在较大的集合上需要更多时间并不奇怪。

您可以尝试以下方法,但收益不会很大,因为它仍然是 O(n)

private SaldoPrivatiefKlantVerdeelsleutel GetParentSaldoPrivatiefKlantVerdeelsleutel(SaldoPrivatiefKlantVerdeelsleutel saldoPrivatiefKlantVerdeelsleutel, SaldoGebouwRekeningBoeking boeking, int privatiefKlant)
{
    SaldoPrivatiefKlantVerdeelsleutel parentSaldoPrivatiefKlantVerdeelsleutel = null;

    if (saldoPrivatiefKlantVerdeelsleutel != null)
    {
        try
        {
            var query = saldoPrivatiefKlantVerdeelsleutel.AfrekenPeriode
                                                         .SaldoPrivatiefKlantVerdeelsleutelCollection
                                                         .Where(s => s.PrivatiefKlant.ID == privatiefKlant);

            if(boeking != null)
            {
                var gebouwVerdeelSleutelId = boeking.SaldoGebouwRekeningVerdeling
                                                    .SaldoGebouwRekening
                                                    .SaldoVerdeelsleutel
                                                    .GebouwVerdeelSleutel
                                                    .ID;

                query = query.Where(s => s.SaldoVerdeelsleutel != null &&
                    s.SaldoVerdeelsleutel.GebouwVerdeelSleutel.ID == gebouwVerdeelSleutelId);
            }
            parentSaldoPrivatiefKlantVerdeelsleutel = query.FirstOrDefault();
        }
        catch (Exception ex)
        { }
    }

    return parentSaldoPrivatiefKlantVerdeelsleutel;
}

这将使代码更加优化,因为只需要一次检查boeking != null,而不是在每个源集合元素上都进行检查。由于嵌套的Where调用被组合起来,它们不会导致性能损失。


2

我会说这个

boeking.SaldoGebouwRekeningVerdeling.SaldoGebouwRekening.SaldoVerdeelsleutel.GebouwVerdeelSleutel.ID

可能是罪魁祸首。尝试在外部缓存,例如:

var id = boeking != null ? boeking.SaldoGebouwRekeningVerdeling.SaldoGebouwRekening.SaldoVerdeelsleutel.GebouwVerdeelSleutel.ID : 0;

在查询中使用 id

(我做了一个假设:那个长链的某个属性表现得“不太聪明”,实际上相当慢)


0

你可以尝试将它写成简单的代码。LINQ使用委托,这就是为什么会有一点性能损失。

                try
                {
                    parentSaldoPrivatiefKlantVerdeelsleutel = null;
                    foreach (var s in saldoPrivatiefKlantVerdeelsleutel.AfrekenPeriode.SaldoPrivatiefKlantVerdeelsleutelCollection)
                    {
                        if ((boeking == null || (s.SaldoVerdeelsleutel != null && (s.SaldoVerdeelsleutel.GebouwVerdeelSleutel.ID == boeking.SaldoGebouwRekeningVerdeling.SaldoGebouwRekening.SaldoVerdeelsleutel.GebouwVerdeelSleutel.ID))) && s.PrivatiefKlant.ID == privatiefKlant)
                        {
                            parentSaldoPrivatiefKlantVerdeelsleutel = s;
                            break;
                        }
                    }
                }
                catch (Exception ex)
                { }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接