代码之家  ›  专栏  ›  技术社区  ›  Marko

如何为Visual Studio优化此Linq查询?

  •  3
  • Marko  · 技术社区  · 14 年前

    我有一个非常复杂的linq-to-sql查询,我需要以某种方式进行优化,因为后台C编译器完全占用了CPU,我无法键入或编辑我的 .cs 文件通常在Visual Studio 2010中(每个字母,尤其是当IntelliSense想要弹出时,都会大大滞后)。

    罪魁祸首是:

    var custFVC = 
        (from cfvc in customer.CustomerFrameVariationCategories
        let lastValue = cfvc.CustomerFrameVariationCategoryValueChanges.Where(cfvcvc => cfvcvc.ChangeDateTime <= this.SelectedDateTime).OrderByDescending(cfvcvc2 => cfvcvc2.ChangeDateTime).FirstOrDefault() ?? new CustomerFrameVariationCategoryValueChange()
        let lastValue2 = cfvc.FrameVariationCategory.FrameVariation.Frame.FrameValueChanges.Where(fvc => fvc.ChangeDateTime <= this.SelectedDateTime).OrderByDescending(fvc2 => fvc2.ChangeDateTime).FirstOrDefault() ?? new FrameValueChange()
        let lastValue3 = cfvc.FrameVariationCategory.FrameVariationCategoryValueChanges.Where(fvcvc => fvcvc.ChangeDateTime <= this.SelectedDateTime).OrderByDescending(fvcvc2 => fvcvc2.ChangeDateTime).FirstOrDefault() ?? new FrameVariationCategoryValueChange()
        let lastValue4 = cfvc.FrameVariationCategory.FrameVariation.FrameVariationModules.Any(fvm => (fvm.FrameModule.FrameModuleValueChanges.Where(fmvc => fmvc.ChangeDateTime <= this.SelectedDateTime).OrderByDescending(fmvc2 => fmvc2.ChangeDateTime).FirstOrDefault() ?? new FrameModuleValueChange()).IsActive == false)
        where lastValue.IsActive == true
        orderby cfvc.FrameVariationCategory.FrameVariation.Frame.Name, cfvc.FrameVariationCategory.Category.Name, cfvc.FrameVariationCategory.FrameVariation.Name
        select new
        {
           cfvc.Id,
           cfvc.FrameVariationCategory,
           lastValue.CoverCoefficient,
           lastValue.NeiserNet,
           PlywoodName = lastValue2.Plywood.Name,
           FrameIsActive = lastValue2.IsActive,
           OwnCost = cfvc.FrameVariationCategory.FrameVariation.FrameVariationModules.Sum(fvm => // sum all frame variation modules
              (lastValue4 ? 0 : fvm.FrameModule.FrameModuleValueChanges.Where(fmvc => fmvc.ChangeDateTime <= this.SelectedDateTime) // if module not active then 0
              .OrderByDescending(fmvc2 => fmvc2.ChangeDateTime).FirstOrDefault().Porolone) + // otherwise get Porolone
               fvm.FrameModule.FrameModuleComponents.Sum(fmc => // add to Porolone sum of all module components
               (fmc.Article.ArticleDetails.Any() ? fmc.Article.ArticleDetails.Sum(ad => // if any article details then use A*L*W*T instead of Amount
               WindowExcel.MultiplyArticleDetailValues(ad.ArticleDetailValueChanges.Where(advc => advc.ChangeDateTime <= this.SelectedDateTime)
              .OrderByDescending(advc2 => advc2.ChangeDateTime).FirstOrDefault() ?? new ArticleDetailValueChange())) : 
               WindowExcel.GetModuleComponentAmount(fmc.FrameModuleComponentValueChanges.Where(fmcvc => fmcvc.ChangeDateTime <= this.SelectedDateTime) // no details = get amount
                 .OrderByDescending(fmcvc2 => fmcvc2.ChangeDateTime).FirstOrDefault() ?? new FrameModuleComponentValueChange())) * // times article values
               WindowExcel.MultiplyArticleValues(fmc.Article.ArticleValueChanges.Where(avc => avc.ChangeDateTime <= this.SelectedDateTime)
               .OrderByDescending(avc2 => avc2.ChangeDateTime).FirstOrDefault() ?? new ArticleValueChange()))),
           Cubes = cfvc.FrameVariationCategory.FrameVariation.FrameVariationModules.Sum(fvm => (fvm.FrameModule.FrameModuleValueChanges.Where(fmvc => fmvc.ChangeDateTime <= this.SelectedDateTime && fmvc.IsActive == true).OrderByDescending(fmvc2 => fmvc2.ChangeDateTime).FirstOrDefault() ?? new FrameModuleValueChange()).Cubes),
           lastValue3.CoverNet,
           lastValue3.CoverGarbage,
           lastValue3.CoverGross,
           lastValue3.CoverPrice,
           lastValue3.BackgroundNet,
           lastValue3.BackgroundGarbage,
           lastValue3.BackgroundGross,
           lastValue3.BackgroundPrice,
           FVCIsActive = lastValue3.IsActive,
           FrameModuleAnyNonActive = lastValue4
        }).ToList();
    

    最大的问题是 OwnCost ,在该Visual Studio之前和之后的所有内容都可以处理。我不想关闭后台编译(在实际编译之前检查编译时错误的功能),我不想创建存储过程。我不能将此代码关闭为单独的类/方法,因为不能传递Linq DataContext(据我所知-还应考虑到上下文变量位于 using 声明)。

    我唯一模糊的想法是某种扩展方法,或者返回LINQ查询的方法,或者类似的方法。因为我不知道我能在这里做些什么来纠正这个问题,我不知道如何制定措辞,所以我不能用谷歌搜索它…

    如何移动(或优化) 自有成本 或者整个查询超出当前 文件,或者将其拆分为同一文件中的方法(可能有助于后台编译器),或者“something”…?

    7 回复  |  直到 14 年前
        1
  •  5
  •   Robert Rossney    14 年前

    我的第一直觉是,您正试图让Linq to SQL完成存储过程的工作。但这可能不正确;很难判断存储过程是否有可能做到这一点。

    我的第二个直觉是应该可以将 OwnCost 计算到函数中,以便此查询只包含

    OwnCost = cfvc.Select(CalculateOwnCost)
    

    我的第三直觉是,当看到计算包括 WindowExcel 目标,是逃离尖叫声,但我要做几次深呼吸,然后问,事实上,您是否在这个查询的上下文中与Excel进行交互操作,这可能是问题的根源吗?

    编辑

    打破 自有成本 计算到自己的函数中,执行如下操作:

    public decimal CalculateOwnCost(CustomerFrameVariationCategory cvfc)
    {
       return cfvc.FrameVariationCategory.FrameVariation.FrameVariationModules.Sum(fvm => // sum all frame variation modules
                     (lastValue4 ? 0 : fvm.FrameModule.FrameModuleValueChanges.Where(fmvc => fmvc.ChangeDateTime <= this.SelectedDateTime) // if module not active then 0
                     .OrderByDescending(fmvc2 => fmvc2.ChangeDateTime).FirstOrDefault().Porolone) + // otherwise get Porolone
                      fvm.FrameModule.FrameModuleComponents.Sum(fmc => // add to Porolone sum of all module components
                      (fmc.Article.ArticleDetails.Any() ? fmc.Article.ArticleDetails.Sum(ad => // if any article details then use A*L*W*T instead of Amount
                      WindowExcel.MultiplyArticleDetailValues(ad.ArticleDetailValueChanges.Where(advc => advc.ChangeDateTime <= this.SelectedDateTime)
                     .OrderByDescending(advc2 => advc2.ChangeDateTime).FirstOrDefault() ?? new ArticleDetailValueChange())) : 
                      WindowExcel.GetModuleComponentAmount(fmc.FrameModuleComponentValueChanges.Where(fmcvc => fmcvc.ChangeDateTime <= this.SelectedDateTime) // no details = get amount
                     .OrderByDescending(fmcvc2 => fmcvc2.ChangeDateTime).FirstOrDefault() ?? new FrameModuleComponentValueChange())) * // times article values
                      WindowExcel.MultiplyArticleValues(fmc.Article.ArticleValueChanges.Where(avc => avc.ChangeDateTime <= this.SelectedDateTime)
                      .OrderByDescending(avc2 => avc2.ChangeDateTime).FirstOrDefault() ?? new ArticleValueChange()))),
                  Cubes = cfvc.FrameVariationCategory.FrameVariation.FrameVariationModules.Sum(fvm => (fvm.FrameModule.FrameModuleValueChanges.Where(fmvc => fmvc.ChangeDateTime <= this.SelectedDateTime && fmvc.IsActive == true).OrderByDescending(fmvc2 => fmvc2.ChangeDateTime).FirstOrDefault() ?? new FrameModuleValueChange()).Cubes)
    }
    

    假设 CustomerFrameVariationCategories 是一个集合 CustomerFrameVariationCategory 对象,以及 自有成本 是一个 decimal .

    完成此操作后,原始查询可以只包含 Select 我上面展示的-你也可以把它写成

    OwnCost = cfvc.Select(x => CalculateOwnCost(x))
    

    如果这让你更舒服的话(我,在这一点上,我已经受够了雷斯哈珀的责骂,我已经接受了,但这是一个品味问题)。

    您没有理由不能进一步将查询中的一些中间表达式分解为它们自己的函数。毕竟,lambda函数只是一个函数。

        2
  •  1
  •   JaredPar    14 年前

    对于C编译器的开销,我没有任何固有的见解来理解这个问题。不过,当我查看您的查询时,有两件事会跳出来,如下所示

    1. 的数量和复杂性 let 绑定
    2. 初始值设定项的复杂性 OwnCost select子句中的成员

    我能给出的最好的建议是尝试将查询拆分成单独的语句,希望这样可以减轻编译器的压力。

        3
  •  1
  •   Patrick Karcher    14 年前

    把它拆开。这是一个巨大的表达树vs正试图处理。您可以将其分解,以便在LinqToObject中发生一些select子句转换。这对于后台编译器来说要容易得多。只是得到:

    var custFVC = (from cfvc in customer.CustomerFrameVariationCategories
                   let lastValue = cfvc.CustomerFrameVariationCategoryValueChanges.Where(cfvcvc => cfvcvc.ChangeDateTime <= this.SelectedDateTime).OrderByDescending(cfvcvc2 => cfvcvc2.ChangeDateTime).FirstOrDefault() ?? new CustomerFrameVariationCategoryValueChange()
                   let lastValue2 = cfvc.FrameVariationCategory.FrameVariation.Frame.FrameValueChanges.Where(fvc => fvc.ChangeDateTime <= this.SelectedDateTime).OrderByDescending(fvc2 => fvc2.ChangeDateTime).FirstOrDefault() ?? new FrameValueChange()
                   let lastValue3 = cfvc.FrameVariationCategory.FrameVariationCategoryValueChanges.Where(fvcvc => fvcvc.ChangeDateTime <= this.SelectedDateTime).OrderByDescending(fvcvc2 => fvcvc2.ChangeDateTime).FirstOrDefault() ?? new FrameVariationCategoryValueChange()
                   let lastValue4 = cfvc.FrameVariationCategory.FrameVariation.FrameVariationModules.Any(fvm => (fvm.FrameModule.FrameModuleValueChanges.Where(fmvc => fmvc.ChangeDateTime <= this.SelectedDateTime).OrderByDescending(fmvc2 => fmvc2.ChangeDateTime).FirstOrDefault() ?? new FrameModuleValueChange()).IsActive == false)
                   where lastValue.IsActive == true
                   orderby cfvc.FrameVariationCategory.FrameVariation.Frame.Name, cfvc.FrameVariationCategory.Category.Name, cfvc.FrameVariationCategory.FrameVariation.Name
                   select new
                   { cfvc, lastValue, lastValue1, lastValue2, lastValue3}).ToList();
    

    然后从那里做剩下的操作。如果结果集很小,那么无论如何,这可能会更有效,而且对数据库来说肯定更容易。如果结果集很小,那么这样做的性能成本很低。

    如果您有一个无聊的、工作不足的数据库,并且有一个很大的结果集,并且运行此代码的机器很紧张,那么您可能需要将工作负载保留在数据库上。

    请记住,只需将构建一个大型表达式树的步骤分解为几个步骤来运行iquerable,对您没有任何好处。最后一个变量将和您现在的一样复杂(在引擎盖下),编译器仍然会阻塞。底线是您需要在这个操作的生命周期的早期运行.tolist()。针对IEnumerable的一系列Linq to对象查询 不会 后台编译器很难处理。

        4
  •  1
  •   dthorpe    14 年前

    Vs可能对此感到窒息,因为它是如此庞大、复杂的单一语句。

    由于owncost和linq上下文之间的唯一链接是对cfvc和lastvalue4的引用,在我看来,您可以在初始linq查询语句之后的单独步骤中计算owncost。将lastValue4存储在由linq语句构造的匿名类型中,并从末尾移除owncost和remove.tolist()。您不需要存储cfvc值,因为您使用它的唯一目的是访问.framevariationcategory,您已经在匿名类型的第二个字段中捕获了它。

    在一个单独的语句中,从custfvc result set中选择以构造每个项的owncost,以生成一个包含您要查找的所有数据位的新结果集。对第二个结果集调用.tolist()。这将在类似的时间内产生与怪物声明相同的结果。

    如果这是一个很大的结果集,请注意多次迭代数据。如果您使用for each为原始结果集中的每个项目计算owncost,那么您将在数据中运行两次,这是单个Monster Linq查询的两倍。

    如果对第二个操作使用LINQ查询,它不应导致对数据进行超出现有范围的任何额外传递-LINQ是延迟计算的,因此在请求之前,不会实际检索下一行。tolist()强制检索所有行。foreach循环强制检索所有行。使用LINQ查询作为输入的LINQ查询不会迭代输入结果集的任何行,它只会堆积更多的条件,以便在最终有人请求第二个结果集的下一行时进行评估。

        5
  •  1
  •   Markus Hütter    14 年前

    这里我可能是错的,这只是一个猜测,但是您是否尝试过用分部关键字拆分类(基本上是您的文件)?

        6
  •  0
  •   Marko    14 年前

    哈,我自己找到了解决办法:)

    罗伯茨对LINQ函数的直觉让我在谷歌上搜索。结果与手头的事情无关,但我偶然发现的一小段代码让我想到了一种暴力攻击方法。使用Redoced的部分类思想,我最终在一个单独的.cs文件中编写了这段代码:

    public partial class WindowExcel
    {
        private static decimal GetOwnCost(CustomerFrameVariationCategory cfvc, bool frameModuleAnyNonActive, DateTime selectedDateTime)
        {
            return cfvc.FrameVariationCategory.FrameVariation.FrameVariationModules.Sum(fvm => // sum all frame variation modules
                (frameModuleAnyNonActive ? 0 : fvm.FrameModule.FrameModuleValueChanges.Where(fmvc => fmvc.ChangeDateTime <= selectedDateTime) // if module not active then 0
                .OrderByDescending(fmvc2 => fmvc2.ChangeDateTime).FirstOrDefault().Porolone) + // otherwise get Porolone
                fvm.FrameModule.FrameModuleComponents.Sum(fmc => // add to Porolone sum of all module components
                    (fmc.Article.ArticleDetails.Any() ? fmc.Article.ArticleDetails.Sum(ad => // if any article details then use A*L*W*T instead of Amount
                        WindowExcel.MultiplyArticleDetailValues(ad.ArticleDetailValueChanges.Where(advc => advc.ChangeDateTime <= selectedDateTime)
                        .OrderByDescending(advc2 => advc2.ChangeDateTime).FirstOrDefault() ?? new ArticleDetailValueChange())) :
                        WindowExcel.GetModuleComponentAmount(fmc.FrameModuleComponentValueChanges.Where(fmcvc => fmcvc.ChangeDateTime <= selectedDateTime) // no details = get amount
                        .OrderByDescending(fmcvc2 => fmcvc2.ChangeDateTime).FirstOrDefault() ?? new FrameModuleComponentValueChange())) * // times article values
                        WindowExcel.MultiplyArticleValues(fmc.Article.ArticleValueChanges.Where(avc => avc.ChangeDateTime <= selectedDateTime)
                        .OrderByDescending(avc2 => avc2.ChangeDateTime).FirstOrDefault() ?? new ArticleValueChange())));
        }
    }
    

    在我的大型Linq查询中,我重写了owncost,如下所示:

    OwnCost = WindowExcel.GetOwnCost(cfvc, lastValue4, this.SelectedDateTime)
    

    编辑getowncost方法仍然非常缓慢,这是例外,但至少我项目的其余部分现在可以使用了。我不知道这种蛮力分离对性能有什么影响。我不能引用customerframevariationcategory,而owncost表达式树位于方法内部而不是在linq查询本身中,这一事实引发了一些问题。我想我必须在某个时候对它进行分析,但这是一个洞的另一个问题。

    现在我们来讨论一下这个微妙的问题,用什么来作为答案。虽然我很欣赏所有的输入,但到目前为止,所有的答案都不正确(没有具体的解决方案),因此我必须在自己的帖子上注明答案。但我会投票赞成瑞德和罗伯特的回答,因为他指出了我的正确方向。

    如果有人能就我的解决方案对原始代码可能产生的代码执行性能影响发表评论,我将不胜感激。

    PS!在InternetExplorer8中写这篇文章的速度又慢得令人痛苦,因为不断占用CPU(这与给代码着色有关)。所以这不仅仅是一个vs问题……

    编辑:

    看来罗伯特已经设法发布了我提出的完全相同的解决方案。如果不是因为不断占用CPU的话,我可能会提前得到我的答案…

    公平地说,我把罗伯特的帖子作为答案:)

        7
  •  0
  •   Peter Mortensen sifr_dot_in    14 年前

    您可以为此编写一个存储过程来执行所有这些操作,而不是将LINQ写入SQL。