有人要我修正N + 1错误?

时间:2012-09-03 23:20:24

标签: c# linq-to-sql

我从未听说过,但人们将应用程序中的问题称为“N + 1问题”。他们正在进行基于Linq to SQL的项目,并且某人已经发现了性能问题。我不太明白 - 但希望有人可以引导我。

似乎他们正试图获取一个obect列表,然后Foreach之后导致了太多的数据库命中:

根据我的理解,源的第二部分仅在转发中加载。

所以,加载的项目列表:

var program = programRepository.SingleOrDefault(r => r.ProgramDetailId == programDetailId);

然后,我们使用此列表:

foreach (var phase in program.Program_Phases)
{
    phase.Program_Stages.AddRange(stages.Where(s => s.PhaseId == phase.PhaseId));
    phase.Program_Stages.ForEach(s =>
    {
         s.Program_Modules.AddRange(modules.Where(m => m.StageId == s.StageId));
    });
    phase.Program_Modules.AddRange(modules.Where(m => m.PhaseId == phase.PhaseId));
}

似乎问题在于,他们希望“程序”包含它的孩子。但是当我们在查询中引用子进程时,它会重新加载proram:

program.Program_Phases

他们期望程序被完全加载并且在内存中,并且profilder似乎表明程序表,所有连接都在每个'foreach'上被调用。

这有意义吗?

(编辑:我发布此链接: Does linq to sql automatically lazy load associated entities? 这可能会回答我的问题,但是......他们正在使用那个更好的(人在...中)表示法,而不是这个奇怪的(x => x ....)。所以如果这个链接是答案 - 也就是说,我们需要'加入'查询 - 可以这样做吗?)

2 个答案:

答案 0 :(得分:4)

在ORM术语中,当您拥有具有嵌套集合属性的实体时,通常会发生“N + 1选择问题”。它指的是在使用延迟加载时将实体数据完全加载到内存中所需的查询数。通常,查询越多,从客户端到服务器的往返越多,服务器处理查询所需的工作就越多,这会对性能产生巨大影响。

有各种技术可以避免这个问题。我不熟悉Linq to SQL,但NHibernate支持急切提取,这在某些情况下有所帮助。如果您不需要加载整个实体实例,那么您还可以考虑进行投影查询。另一种可能性是更改实体模型以避免嵌套集合。

答案 1 :(得分:2)

对于高性能的linq,首先要确切了解您真正关心的属性。 linq在性能方面的一个优点是你可以轻松地省去你不会使用的数据的检索(你总是可以手工编写比linq更好的东西,但linq可以很容易地做到这一点而不创建一个图书馆里有数百个课程,每次都会遗漏一些细节。

你说“列表”几次。如果您不需要,请不要获取列表,只要您不止一次重复使用相同的列表。否则,一次处理一个项目将在99%的时间内表现更好。

你说的“好”和“奇怪”的语法是说同样的事情的不同方式。有些东西只能用方法和lambdas来表达,但另一种形式总是可以这样表达 - 实际上是在编译之后。无论如何,from b in someSource where b.id == 21 select b.name之类的内容都会编译为someSource.Where(b => b.id == 21).Select(b => b.name)

您可以设置DataContext.LoadOptions以准确定义要使用哪个对象加载哪些实体(最重要的是,在不同的用途中非常容易地设置它)。这可以解决您的N + 1问题。

或者,您可能会发现通过自己设置适当的ID来更新现有实体或插入新实体更容易。

即使您没有发现它更容易,在某些情况下也会更快。通常情况下,我会说更容易,忘记性能,只要这个选择,但如果性能是一个问题(你说它是),那么可以进行分析,看看哪两个更好,值得