繁体   English   中英

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

[英]How to optimize this LINQ query for Visual Studio?

我有一个巨大的复杂的LINQ to SQL查询,我需要以某种方式进行优化,因为后台C#编译器完全占用CPU而我无法在Visual Studio 2010中正常输入或编辑我的.cs文件(每个字母,特别是如果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查询或类似的方法。 因为我不知道我能在这里做什么来纠正这个问题,我不知道如何制定措辞,因此我不能谷歌...

如何从当前.cs文件中移出(或优化) OwnCost或整个查询,或者将其拆分为同一文件中的方法(可能有助于后台编译器),或“某事”......?

我的第一直觉是你试图使LINQ to SQL做存储过程的工作。 但这可能是不正确的; 很难判断存储过程是否有可能实现这一点。

我的第二直觉是应该可以将OwnCost计算拆分为一个函数,以便该查询只包含

OwnCost = cfvc.Select(CalculateOwnCost)

看到计算中包含一个WindowExcel对象,我的第三个本能就是逃避尖叫,但我要WindowExcel并问一下,你是否实际上在这个查询的上下文中与Excel互操作,并且可能那可能是问题的根源?

编辑

要将OwnCost计算分解为自己的函数,请执行以下操作:

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)
}

这假设CustomerFrameVariationCategoriesCustomerFrameVariationCategory对象的集合,并且OwnCostdecimal

执行此操作后,您的原始查询可以包含我在上面显示的Select - 您也可以将其编写为

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

如果它让你更舒服(我,我已经被Resharper骂了这一点,我已经接受了它,但这是一个品味的问题)。

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

就C#编译器的代价而言,我对这个问题没有任何固有的见解。 但是,当我查看您的查询时跳出的两件事情如下

  1. let绑定的数量和复杂性
  2. select子句中OwnCost成员的初始化程序的复杂性

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

拆分它。 这是VS正试图处理的一个巨大的表达树。 您可以将其分解,以便在LINQ-to-object中发生一些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();

然后从那里做其余的操作。 如果结果集很小,那么无论如何这可能更有效,并且在db上肯定更容易。 如果结果集很小,那么这样做的性能成本非常低。

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

请记住,只需要分成几个步骤,构建一个对IQuerable运行的大型表达式树对你没有任何帮助。 最后一个变量将像你现在一样复杂(引擎盖下),编译器仍然会窒息。 底线是您需要在此操作的生命周期中运行.ToList()。 针对IEnumerable的一系列LINQ到对象查询对于后台编译器来说并不难以处理。

VS可能会对此感到窒息,因为它是如此庞大,复杂的单一陈述。

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

在单独的语句中,从CustFVC结果集中选择以为每个项构造OwnCost,以生成包含您要查找的所有数据位的新结果集。 在第二个结果集上调用.ToList()。 这应该在相似的时间内产生与怪物陈述相同的结果。

如果这是一个大的结果集,请注意多次迭代数据。 如果您使用foreach为原始结果集中的每个项目计算OwnCost,您将运行两次数据 - 两倍于单个怪物LINQ查询的工作量。

如果你对第二个操作使用LINQ查询,它不应该导致超出你已经拥有的数据的任何额外传递 - LINQ是惰性求值的,所以在被要求之前实际上不会检索下一行。 ToList()强制检索所有行。 foreach循环强制检索所有行。 使用LINQ查询作为输入的LINQ查询不会迭代输入结果集的任何行,当有人最终要求第二个结果集的下一行时,它只会堆积更多要评估的条件。

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

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

哈,我自己找到了一个解决方案:)

罗伯茨关于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查询本身的方法内部,这引发了一些问题。 猜猜我不得不在某个时候对其进行分析,但那是另一个问题。

现在到了什么标记作为答案的微妙问题。 虽然我确实感谢所有的输入,但到目前为止没有一个答案是正确的(没有具体的解决方案),因此我必须将自己的帖子标记为答案。 但我会投票给redoced和Robert的答案,指出我正确的方向。

如果有人可以评论我的解决方案与原始代码相比可能的代码执行性能影响,我将不胜感激。

PS! 在Internet Explorer 8中写这个是非常缓慢的,因为持续的CPU占用(与代码着色有关)。 所以这不仅仅是一个VS问题....

编辑:

罗伯特似乎设法发布了我想出的完全相同的解决方案。 如果没有持续的CPU占用,可能会早点发布我的答案...

平心而论,我将Robert的帖子标记为答案:)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM