【问题标题】:Improving performance of linq query提高 linq 查询的性能
【发布时间】:2012-12-14 11:52:03
【问题描述】:

我正在优化一个包含多个 Linq 查询的方法。到目前为止,执行时间约为 3 秒,我正在尝试减少它。该方法中发生了很多操作和计算,但没有太复杂。

我将不胜感激任何关于如何提高性能和优化代码的建议和想法。


方法的全部代码(下面我会指出我延迟最大的地方):

public ActionResult DataRead([DataSourceRequest] DataSourceRequest request)
{
    CTX.Configuration.AutoDetectChangesEnabled = false;

    var repoKomfortaktion = new KomfortaktionRepository();
    var komfortaktionen = CTX.Komfortaktionen.ToList();

    var result = new List<AqGeplantViewModel>();

    var gruppen = new HashSet<Guid?>(komfortaktionen.Select(c => c.KomfortaktionsGruppeId).ToList());

    var hochgeladeneKomplettabzuege = CTX.Komplettabzug.Where(c => gruppen.Contains(c.KomfortaktionsGruppeId)).GroupBy(c => new { c.BetriebId, c.KomfortaktionsGruppeId }).Select(x => new { data = x.Key }).ToList();
    var teilnehmendeBetriebe = repoKomfortaktion.GetTeilnehmendeBetriebe(CTX, gruppen);
    var hochgeladeneSperrlistenPlz = CTX.SperrlistePlz.Where(c => gruppen.Contains(c.KomfortaktionsGruppeId) && c.AktionsKuerzel != null)
                                                                        .GroupBy(c => new { c.AktionsKuerzel, c.BetriebId, c.KomfortaktionsGruppeId }).Select(x => new { data = x.Key }).ToList();

    var hochgeladeneSperrlistenKdnr = CTX.SperrlisteKdnr.Where(c => gruppen.Contains(c.KomfortaktionsGruppeId) && c.AktionsKuerzel != null)
                                                                        .GroupBy(c => new { c.AktionsKuerzel, c.BetriebId, c.KomfortaktionsGruppeId }).Select(x => new { data = x.Key }).ToList();

    var konfigsProAktion = CTX.Order.GroupBy(c => new { c.Vfnr, c.AktionsId }).Select(c => new { count = c.Count(), c.Key.AktionsId, data = c.Key }).ToList();
    foreach (var komfortaktion in komfortaktionen)
    {
        var item = new AqGeplantViewModel();

        var zentraleTeilnehmer = teilnehmendeBetriebe.Where(c => c.TeilnahmeStatus.Any(x => x.KomfortaktionId == komfortaktion.Id && x.AktionsTypeId == 1)).ToList();
        var lokaleTeilnehmer = teilnehmendeBetriebe.Where(c => c.TeilnahmeStatus.Any(x => x.KomfortaktionId == komfortaktion.Id && x.AktionsTypeId == 2)).ToList();

        var hochgeladeneSperrlistenGesamt =
                hochgeladeneSperrlistenPlz.Count(c => c.data.AktionsKuerzel == komfortaktion.Kuerzel && c.data.KomfortaktionsGruppeId == komfortaktion.KomfortaktionsGruppeId) +
                hochgeladeneSperrlistenKdnr.Count(c => c.data.AktionsKuerzel == komfortaktion.Kuerzel && c.data.KomfortaktionsGruppeId == komfortaktion.KomfortaktionsGruppeId);

        item.KomfortaktionId = komfortaktion.KomfortaktionId;
        item.KomfortaktionName = komfortaktion.Aktionsname;
        item.Start = komfortaktion.KomfortaktionsGruppe.StartAdressQualifizierung.HasValue ? komfortaktion.KomfortaktionsGruppe.StartAdressQualifizierung.Value.ToString("dd.MM.yyyy") : string.Empty;
        item.LokalAngemeldet = lokaleTeilnehmer.Count();
        item.ZentralAngemeldet = zentraleTeilnehmer.Count();

        var anzHochgelandenerKomplettabzuege = hochgeladeneKomplettabzuege.Count(c => zentraleTeilnehmer.Count(x => x.BetriebId == c.data.BetriebId) == 1) +
                                               hochgeladeneKomplettabzuege.Count(c => lokaleTeilnehmer.Count(x => x.BetriebId == c.data.BetriebId) == 1);

        item.KomplettabzugOffen = (zentraleTeilnehmer.Count() + lokaleTeilnehmer.Count()) - anzHochgelandenerKomplettabzuege;

        item.SperrlisteOffen = (zentraleTeilnehmer.Count() + lokaleTeilnehmer.Count()) - hochgeladeneSperrlistenGesamt;

        item.KonfigurationOffen = zentraleTeilnehmer.Count() - konfigsProAktion.Count(c => c.AktionsId == komfortaktion.KomfortaktionId && zentraleTeilnehmer.Any(x => x.Betrieb.Vfnr == c.data.Vfnr));

        item.KomfortaktionsGruppeId = komfortaktion.KomfortaktionsGruppeId;
        result.Add(item);
    }

    return Json(result.ToDataSourceResult(request));
}

前半部分(在 foreach 之前)需要半秒,这没关系。最大的延迟是在 first 迭代中的 foreach 语句内部,特别是在这些行中,zentraleTeilnehmer 的执行第一次需要 1.5 秒。

var zentraleTeilnehmer = teilnehmendeBetriebe.Where(c => c.TeilnahmeStatus.Any(x => x.KomfortaktionId == komfortaktion.Id && x.AktionsTypeId == 1)).ToList();
var lokaleTeilnehmer = teilnehmendeBetriebe.Where(c => c.TeilnahmeStatus.Any(x => x.KomfortaktionId == komfortaktion.Id && x.AktionsTypeId == 2)).ToList();

TeilnehmendeBetriebe 有 800 多行,其中 TeilnahmeStatus 属性通常有 4 项左右。所以,最多 800*4 次迭代,毕竟这不是一个巨大的数字……

因此,我主要对优化这些行感兴趣,希望将执行时间减少到半秒左右。

我尝试了什么:

  1. 将 Linq 重写为 foreach:没有帮助,同时...可能并不奇怪,但值得一试。

    foreach (var tb in teilnehmendeBetriebe) //836 items
    {
        foreach (var ts in tb.TeilnahmeStatus) //3377 items
        {
            if (ts.KomfortaktionId == komfortaktion.Id && ts.AktionsTypeId == 1)
            {
                testResult.Add(tb);
                break;
            }
        }
    }
    
  2. 使用 .Select() 为 teilnehmendeBetriebe 选择特定列。也没有用。

我尝试过的其他小操作都没有帮助。

有趣的是 - 虽然 foreach 的第一次迭代可能需要 2 秒,但第二次和进一步只需要毫秒,因此 .net 能够优化或重用计算数据。

非常欢迎任何有关可以更改以提高性能的建议!

编辑:
TeilnahmeBetriebKomfortaktion.TeilnahmeStatus 在方法GetTeilnehmendeBetriebe 中急切加载:

    public List<TeilnahmeBetriebKomfortaktion> GetTeilnehmendeBetriebe(Connection ctx, HashSet<Guid?> gruppen)
    {
        return ctx.TeilnahmeBetriebKomfortaktion.Include(
                                        c => c.TeilnahmeStatus).ToList();
    }

编辑2: 执行 GetTeilnehmendeBetriebe 时发送的查询:

    SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[BetriebId] AS [BetriebId], 
    [Extent1].[MandantenId] AS [MandantenId], 
    [Extent1].[CreatedUser] AS [CreatedUser], 
    [Extent1].[UpdatedUser] AS [UpdatedUser], 
    [Extent1].[CreatedDate] AS [CreatedDate], 
    [Extent1].[UpdatedDate] AS [UpdatedDate], 
    [Extent1].[IsDeleted] AS [IsDeleted]
    FROM [Semas].[TeilnahmeBetriebKomfortaktion] AS [Extent1]
    WHERE [Extent1].[IsDeleted] <> cast(1 as bit)

【问题讨论】:

  • 我们在这里谈论的是 LINQ to Entities 吗? repoKomfortaktion.GetTeilnehmendeBetriebe 返回什么类型?
  • @Daniel 是的,Linq to Entities。它返回 List
  • 如果您向我们展示CTX.Komfortaktionen 查询可能会有所帮助。
  • "Entity Framework 支持延迟加载相关实体。在Entity Framework 运行时,ObjectContext 实例中的LazyLoadingEnabled 属性的默认值为false。但是,如果您使用Entity Framework 工具为了创建一个新模型和相应的生成类,在对象上下文的构造函数中将 LazyLoadingEnabled 设置为 true。” - msdn.microsoft.com/en-us/library/bb896272(v=vs.100).aspx

标签: c# performance linq


【解决方案1】:

我的假设是TeilnahmeBetriebKomfortaktion.TeilnahmeStatus 是一个延迟加载的集合,导致N + 1 problem。您应该急切地获取该集合以提高您的性能。

foreach 循环的以下迭代很快,因为在第一次迭代之后,这些对象不再从数据库服务器请求,而是从内存服务器。

【讨论】:

  • 这个主意真不错!但是,不幸的是,它已经提前加载了..(我在问题描述的末尾添加了这些细节)
  • @Anelook:嗯...你能检查一下实际发送到服务器的查询吗?
  • 我检查了发送的查询。确实,我看不到 TeilnahmeStatus 的连接位置。我将查询添加到问题的描述中。我相信问题确实出在 TeilnahmeStatus 的检索方式上。
猜你喜欢
  • 1970-01-01
  • 2012-07-09
  • 1970-01-01
  • 2016-01-20
  • 1970-01-01
  • 1970-01-01
  • 2019-02-04
  • 1970-01-01
  • 2013-08-05
相关资源
最近更新 更多