【发布时间】: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 秒。所以我现在有两个问题。
- 为什么 lambda 这么慢?
- 我能否改进我的 LINQ 查询,使其运行速度超过设置 400 个小(4-9 个基本值类型属性)对象所需的 0.6 秒?
提前致谢!
乔纳森。
【问题讨论】:
-
顺便说一句,
readonly List<T>真的不是只读的。它只会阻止您为其重新分配价值,但您始终可以为它分配Clear或Add项。
标签: c# .net performance linq lambda