使用LINQ性能重建Parent-> Child-> GrandChild层次结构

时间:2014-05-29 21:38:08

标签: c# .net performance linq lambda

问题

我有一个层次结构,我从数据库中提取并尝试使用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秒,但每个BidItems都包含一个空的SubItem集合。出于某种原因,我的SubItems没有像他们应该那样填充BidItem视图模型构造函数。我在subItemCollection.Where() lambda中设置了一个断点,它永远不会被击中。

我真的很感谢我对LINQ做错的一些指导。我对LINQ有点新意,所以我知道我做错了,这是一个简单的修复。

编辑:

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

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

  1. 为什么lambda这么慢?
  2. 我可以改进LINQ查询,使其运行速度超过设置400个小型(4-9基本值类型属性)对象所花费的0.6秒吗?
  3. 提前致谢!

    乔纳森。

1 个答案:

答案 0 :(得分:1)

为什么lambda这么慢?

projectSections.Select(section => 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();
    }
}