首先,必须说这不是一个简单的查询。看起来我们有:
- 通过嵌套问答树进行 6 级递归
- 总共20张表通过eagerloaded以这种方式连接起来
.Include
我首先会花时间确定此查询在您的应用程序中的何处使用以及需要的频率,特别要注意它最常使用的位置。
雅格尼优化
明显的起点是查看应用程序中使用查询的位置,如果您并不总是需要整个树,那么建议您不要加入嵌套的问题和答案表(如果不需要)在查询的所有用法中。
另外,还可以作曲IQueryable
动态地,所以如果您的查询有多个用例(例如,来自不需要问题+答案的“摘要”屏幕,以及需要它们的详细信息树),那么您可以执行以下操作:
var questionnaireQuery = _myContext.Questionnaires
.Include(q => q.Sections)
.Include(q => q.QuestionnaireCommonFields);
// Conditionally extend the joins
if (mustIncludeQandA)
{
questionnaireQuery = questionnaireQuery
.Include(q => q.Sections.Select(s => s.Questions.Select(q => q.Answers..... etc);
}
// Execute + materialize the query
var questionnaires = await questionnaireQuery
.Where(q => questionnaireIds.Contains(q.Id))
.ToListAsync()
.ConfigureAwait(false);
SQL优化
如果您确实必须始终获取整个树,那么请查看您的 SQL 表设计和索引。
1) 过滤器
.Where(q => questionnaireIds.Contains(q.Id))
(我在这里假设使用 SQL Server 术语,但这些概念也适用于大多数其他 RDBM。)
我正在猜测Questionnaires.Id
是一个聚集主键,因此将被索引,但只需检查完整性(它看起来像是PK_Questionnaires CLUSTERED UNIQUE PRIMARY KEY
在SSMS中)
2) 确保所有子表的外键上都有返回父表的索引。
e.g. q => q.Sections
意味着该表Sections
有一个外键返回Questionnaires.Id
- 确保其上至少有一个非聚集索引 - EF Code First 应该自动执行此操作,但再次检查以确保确定。
这看起来像IX_QuestionairreId NONCLUSTERED
在列上Sections(QuestionairreId)
3) 考虑更改子表上的聚集索引,使其按其父级的外键聚集,例如簇Section
by Questions.SectionId
。这会将与同一父级相关的所有子行保留在一起,并减少 SQL 需要获取的数据页数。这不是微不足道的 https://stackoverflow.com/q/33765908/314291首先在 EF 代码中实现,但您的 DBA 可以帮助您完成此操作,也许作为自定义步骤。
其他的建议
如果这个查询只是用来查询数据,而不是更新或删除,那么添加.AsNoTracking()
会略微降低 EF 的内存消耗和内存性能。
与性能无关,但您混合了弱类型(“部分”)和强类型.Include
陈述(q => q.QuestionnaireCommonFields
)。我建议转向强类型包含以获得额外的编译时安全性。
请注意,您只需指定急切加载的最长链的包含路径 - 这显然会强制 EF 也包含所有更高级别。即你可以减少 20.Include
语句仅 2。这将更有效地完成相同的工作:
.Include(q => q.QuestionnaireCommonFields)
.Include(q => q.Sections.Select(s => s.Questions.Select(q => q.Answers .... etc))
你需要.Select
任何时候都存在 1:Many 关系,但如果导航是 1:1(或 N:1),那么您不需要 .Select,例如City c => c.Country
Redesign
最后但并非最不重要的一点是,如果数据仅从顶层过滤(即,Questionnaires
),如果整个问卷“树”(聚合根)通常总是一次性添加或更新,那么您可以尝试在一个问题和答案树中进行数据建模NoSQL
方式,例如通过简单地将整个树建模为 XML 或 JSON,然后将整个树视为一个长字符串。这将完全避免所有令人讨厌的连接。您需要在数据层中执行自定义反序列化步骤。如果您需要从树中的节点进行过滤(即像这样的查询),后一种方法不会很有用找到我所有问题 5 的子答案是“Foo”的问卷不太适合)