【问题标题】:Rebuild Parent->Child->GrandChild hierarchy with LINQ performance使用 LINQ 性能重建 Parent->Child->GrandChild 层次结构
【发布时间】:2014-05-29 21:38:08
【问题描述】:

问题

我有一个从数据库中提取并尝试使用LINQ 恢复的层次结构。当我对集合运行 LINQ 查询时,它似乎没有击中我的孙子对象。

对象设置

我的对象的层次结构如下

One Project -> Many Sections
One Section -> Many BidItems
One BidItem -> Many SubItems

它们都通过数据库中的外键关联并映射到我的模型对象。以下是模型的简化版本。

型号

public class Section
{
    public int SectionId { get; set; }

    public int ProjectId { get; set; }
}

public class BidItem
{
    public int BidItemId { get; set; }

    public int SectionId { get; set; }
}

public class SubItem
{
    public int SubItemId { get; set; }

    public int BidItemId { get; set; }
}

查看模型

public class SectionViewModel : BaseChangeNotify
{
    private readonly Section section;
    private readonly List<BidItemViewModel> bidItems;

    public SectionViewModel(Project project, Section section)
    {
        var repository = new ProjectRepository();

        this.section = section;

        this.bidItems = new List<BidItemViewModel>(
            (from item in repository.GetBidItemsBySectionId(section.SectionId)
             select new BidItemViewModel(project, item)).ToList());
    }

    public SectionViewModel(Project project, Section section, List<BidItemViewModel> bidItemsForSection)
    {
        this.section = section;
        this.bidItems = bidItemsForSection;
    }
}

public class BidItemViewModel : BaseChangeNotify
{
    private BidItem bidItem;

    private List<SubItem> subItems;

    public BidItemViewModel(Project project, BidItem bidItem, List<SubItem> subItems = null)
    {
        var repository = new ProjectRepository();

        this.bidItem = bidItem;

        if (subItems == null)
        {
            subItems = repository.GetSubItemsByBidItemId(bidItem.BidItemId);
        }

        this.subItems = subItems;
    }
}

您可以在每个视图模型的一个构造函数中看到,我正在访问一个存储库来获取子对象。我想重写它,因为它表现不佳。可能有十几个 sections,每个都有 100+ BidItems。每个BidItem 可以有100+ SubItems。所以对于一个有 5 个部分的项目,我在应用程序启动期间访问了数据库 50,000 次(大约需要 2.9 秒)。

麻烦的源代码

我已经对其进行了重构,因此我只进行了 3 次调用,一次用于获取项目的所有部分,一次用于获取项目中的所有 BidItems,另一次用于获取项目中的所有 SubItems。现在我需要重建层次结构。

我最初尝试使用 Lambda:

List<Section> projectSections =
    repository.GetSectionsByProjectId(ProjectId).Where(section => section.SectionId != 0).ToList();
List<BidItem> bidItemCollection = repository.GetBidItemsByProjectId(ProjectId);
List<SubItem> subItemCollection = repository.GetSubItemsByProjectId(ProjectId);

// After the database calls so I can test actual reconstruction performance.
timer.Start();

foreach (var sectionViewModel in projectSections.Select(section => new SectionViewModel(project, section)))
{
    Parallel.ForEach(bidItemCollection
        .Where(bidItem => bidItem.SectionId == sectionViewModel.SectionId), bidItem => 
        {
            var bidItemViewModel = new BidItemViewModel(project, bidItem,   
                subItemCollection.Where(subItem => subItem.BidItemId == bidItem.BidItemId).ToList());

            sectionViewModel.BidItems.Add(bidItemViewModel);
        });

    sectionViewModels.Add(sectionViewModel);
}
timer.Stop();

这很好用,但速度很慢。我最初的方法在启动过程中需要 2.9 秒才能返回所有 Sections、BidItems 和 SubItems。 Lambda 耗时 2.3 秒。然后我尝试了一个 LINQ 查询。

List<Section> projectSections =
    repository.GetSectionsByProjectId(ProjectId).ToList();
List<BidItem> bidItemCollection = repository.GetBidItemsByProjectId(ProjectId);
List<SubItem> subItemCollection = repository.GetSubItemsByProjectId(ProjectId);
timer.Start();

sectionViewModels = new List<SectionViewModel>(
    from section in projectSections
    select new SectionViewModel(
        project,
        section,
        bidItemCollection.Where(c => c.SectionId == section.SectionId)
            .Select(
                bidItem =>
                    new BidItemViewModel(project, bidItem,
                        new List<SubItem>(
                            subItemCollection.Where(subItem => subItem.BidItemId == bidItem.BidItemId))))
            .ToList()));
timer.Stop();

这返回最快,为 0.3 秒,但每个 BidItem 都包含一个空的 SubItem 集合。出于某种原因,我的 SubItems 没有像应有的那样填充 BidItem 视图模型构造函数。我在subItemCollection.Where() lambda 中设置了一个断点,它永远不会被命中。

我非常感谢一些关于我在 LINQ 上做错了什么的指导。我对 LINQ 有点陌生,所以我知道我做错了什么,这是一个简单的修复。

编辑:

所以看来问题是我的 LINQ 查询单元测试使用了错误的存储过程(就像我的 Lambda 一样)来获取子项,导致 inn 零回报。我已经解决了这个问题,现在我得到了所有三个变体的匹配数字。

有趣的是现在的结果。第一种方法,访问数据库 500 次需要 1.89 秒。 Lambda 需要 2.3 秒来重建 3 个数据库查询。 LINQ 需要 0.70 秒。对于 Lambda 和 LINQ 单元测试,我的数据库查询(使用 Dapper)需要 0.11 秒。所以我现在有两个问题。

  1. 为什么 lambda 这么慢?
  2. 我能否改进我的 LINQ 查询,使其运行速度超过设置 400 个小(4-9 个基本值类型属性)对象所需的 0.6 秒?

提前致谢!

乔纳森。

【问题讨论】:

  • 顺便说一句,readonly List&lt;T&gt; 真的不是只读的。它只会阻止您为其重新分配价值,但您始终可以为它分配 ClearAdd 项。

标签: c# .net performance linq lambda


【解决方案1】:

为什么 lambda 这么慢?

projectSections.Select(section =&gt; new SectionViewModel(project, section))

from section in projectSections
select new SectionViewModel(
    project,
    section,
    bidItemCollection.Where(...).ToList()

这两个调用不同的构造函数,因此执行时间不同。

只要逻辑和方法相同,两种写法都应该同时给出相同的结果。因为,编译器会生成相同的 IL。

如何优化?

由于我无法在您的机器上进行基准测试,我将仅使用一般假设。

  • 通常执行拉取所有需要的数据比拉取多次要好。 所以我会避免调用SectionViewModel.ctor(Project,Section)BidItemViewModel.ctor(Project,BidItem),因为它们会在数据库中执行更多查询。

话虽如此,我会把我的 lambda 写成如下://actually this is just your 3rd piece of code cleaned up

sectionViewModels = new List<SectionViewModel>(
        projectSections.Select(
            s => new SectionViewModel(project, s, bidItemCollection.Where(b => b.SectionId == s.SectionId).Select(
                    b => new BidItemViewModel(project, b, subItemCollection.Where(si => si.BidItemId == b.BidItemId))))));

另外,为了美观,我更改了以下构造函数以避免在 Lambda 中间出现 ToList

public class SectionViewModel
{
    private readonly Section section;
    private readonly List<BidItemViewModel> bidItems;

    public SectionViewModel(Project project, Section section, IEnumerable<BidItemViewModel> bidItemsForSection)
    {
        this.section = section;
        this.bidItems = bidItemsForSection.ToList();
    }
}

public class BidItemViewModel
{
    private BidItem bidItem;
    private List<SubItem> subItems;

    public BidItemViewModel(Project project, BidItem bidItem, IEnumerable<SubItem> subItems)
    {
        this.bidItem = bidItem;
        this.subItems = subItems.ToList();
    }
}

【讨论】:

  • 感谢Xiaoy312,我没有意识到我使用了错误的构造函数!我对 IEnumerable 进行了构造函数更改并修改了我的 LINQ 查询以匹配您的查询,并且我的单元测试时间从 750 毫秒减少了一半到 375 毫秒。非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-10
  • 2018-10-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多