提高大型 EF 多级包含的性能

2024-04-15

我是一名 EF 菜鸟(就像我今天刚开始,我只使用过其他 ORM),我正在经历一场烈火的洗礼。

我被要求提高另一个开发人员创建的查询的性能:

      var questionnaires = await _myContext.Questionnaires
            .Include("Sections")
            .Include(q => q.QuestionnaireCommonFields)
            .Include("Sections.Questions")
            .Include("Sections.Questions.Answers")
            .Include("Sections.Questions.Answers.AnswerMetadatas")
            .Include("Sections.Questions.Answers.SubQuestions")
            .Include("Sections.Questions.Answers.SubQuestions.Answers")
            .Include("Sections.Questions.Answers.SubQuestions.Answers.AnswerMetadatas")
            .Include("Sections.Questions.Answers.SubQuestions.Answers.SubQuestions")
            .Include("Sections.Questions.Answers.SubQuestions.Answers.SubQuestions.Answers")
            .Include("Sections.Questions.Answers.SubQuestions.Answers.SubQuestions.Answers.AnswerMetadatas")
            .Include("Sections.Questions.Answers.SubQuestions.Answers.SubQuestions.Answers.SubQuestions")
            .Include("Sections.Questions.Answers.SubQuestions.Answers.SubQuestions.Answers.SubQuestions.Answers")
            .Include("Sections.Questions.Answers.SubQuestions.Answers.SubQuestions.Answers.SubQuestions.Answers.AnswerMetadatas")
            .Include("Sections.Questions.Answers.SubQuestions.Answers.SubQuestions.Answers.SubQuestions.Answers.SubQuestions")
            .Include("Sections.Questions.Answers.SubQuestions.Answers.SubQuestions.Answers.SubQuestions.Answers.SubQuestions.Answers")
            .Include("Sections.Questions.Answers.SubQuestions.Answers.SubQuestions.Answers.SubQuestions.Answers.SubQuestions.Answers.AnswerMetadatas")
            .Include("Sections.Questions.Answers.SubQuestions.Answers.SubQuestions.Answers.SubQuestions.Answers.SubQuestions.Answers.SubQuestions")
            .Include("Sections.Questions.Answers.SubQuestions.Answers.SubQuestions.Answers.SubQuestions.Answers.SubQuestions.Answers.SubQuestions.Answers")
            .Include("Sections.Questions.Answers.SubQuestions.Answers.SubQuestions.Answers.SubQuestions.Answers.SubQuestions.Answers.SubQuestions.Answers.AnswerMetadatas")
        .Where(q => questionnaireIds.Contains(q.Id))
        .ToListAsync().ConfigureAwait(false);

快速的网络浏览告诉我,如果您运行多个深度级别,则 Include() 会产生 cols * rows 产品,并且性能会很差。

我在 SO 上看到了一些有用的答案,但它们的例子有限,而且我无法找出重写上述内容的最佳方法。

该部分的多次重复 -“Sections.Questions.Answers.SubQuestions.Answers.SubQuestions.Answers...”对我来说看起来很可疑,就像它可以单独完成,然后发出另一个查询,但我不知道如何构建或者这种方法是否会提高性能。

问题:

  1. 如何将此查询重写为更合理的查询以提高性能,同时确保最终结果集相同?

  2. 鉴于最后一行:.Include("Sections.Questions.Answers.SubQuestions.Answers.SubQuestions.Answers.SubQuestions.Answers.SubQuestions.Answers.SubQuestions.Answers.AnswerMetadatas")
    为什么我需要所有中间线? (我猜这是因为某些连接可能不是左连接?)

EF 版本信息:包 id="EntityFramework" version="6.2.0" targetFramework="net452"

我意识到这个问题有点垃圾,但我试图从无知识的角度尽快解决。

Edit

经过半天的思考,并感谢 StuartLC 的建议,我想出了一些选择:

差 - 拆分查询,以便它执行多次往返来获取数据。这可能会为用户提供稍慢的体验,但会阻止 SQL 超时。 (这并不比仅仅增加 EF 命令超时好多少)。

好 - 将子表上的聚集索引更改为按其父级的外键聚集(假设您没有大量插入操作)。

好 - 将代码更改为仅查询前几个级别并延迟加载(单独的数据库命中)低于此级别的任何内容,即删除除前几个包含之外的所有内容,然后更改 ICollections - Answers.SubQuestions、Answers.AnswerMetadatas 和 Question。所有的答案都是虚拟的。据推测,使这些虚拟化的缺点是,如果应用程序中的任何(其他)现有代码期望这些 ICollection 属性被急切加载,您可能必须更新该代码(即,如果您希望/需要它们在该代码中立即加载) )。我将进一步研究这个选项。进一步编辑 - 不幸的是,如果您由于自引用循环而需要序列化响应,则这将不起作用。

不简单 - 手动编写一个 sql 存储过程/视图并构建一个指向它的新 EF 对象。

长期来看

显而易见、最好但最耗时的选项 - 重写应用程序设计,因此不需要在单个 api 调用中使用整个数据树,或者使用以下选项:

重写应用程序以 NoSQL 方式存储数据(例如,将对象树存储为 json,这样就没有连接)。正如斯图尔特提到的,如果您需要以其他方式(通过调查表 ID 以外的方式)过滤数据,那么这不是一个好的选择,而您可能需要这样做。另一种替代方法是根据需要部分存储 NoSQL 样式,部分存储关系型。


首先,必须说这不是一个简单的查询。看起来我们有:

  • 通过嵌套问答树进行 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”的问卷不太适合)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

提高大型 EF 多级包含的性能 的相关文章

  • 在 C++ 中分割大文件

    我正在尝试编写一个程序 该程序接受一个大文件 任何类型 并将其分成许多较小的 块 我想我已经有了基本的想法 但由于某种原因我无法创建超过 12 kb 的块大小 我知道谷歌等上有一些解决方案 但我更感兴趣的是了解这个限制的根源是什么 然后实际
  • 如何进行带有偏差的浮点舍入(始终向上或向下舍入)?

    我想以偏置舍入浮动 要么总是向下 要么总是向上 代码中有一个特定的点 我需要这个 程序的其余部分应该像往常一样四舍五入到最接近的值 例如 我想四舍五入到最接近的 1 10 倍数 最接近 7 10 的浮点数约为 0 69999998807 但
  • Blazor 与 Razor

    随着 Blazor 的发明 我想知道这两种语言之间是否存在显着的效率 无论是在代码创建方面还是在代码的实际编译 执行方面 https github com SteveSanderson Blazor https github com Ste
  • TextBox 焦点的 WinForms 事件?

    我想添加一个偶数TextBox当它有焦点时 我知道我可以用一个简单的方法来做到这一点textbox1 Focus并检查布尔值 但我不想那样做 我想这样做 this tGID Focus new System EventHandler thi
  • 调试内存不足异常

    在修复我制作的小型 ASP NET C Web 应用程序的错误时 我遇到了 OutOfMemoryException 没有关于在哪里查看的提示 因为这是一个编译时错误 如何诊断此异常 我假设这正是内存分析发挥作用的地方 有小费吗 Thank
  • VS30063:您无权访问 https://dev.azure.com

    我正在尝试在 asp net core 2 1 mvc 应用程序中使用以下代码连接 Azure DevOps Uri orgUrl new Uri https dev azure com xxxxx String personalAcces
  • C++11 函数局部静态 const 对象的线程安全初始化

    这个问题已在 C 98 上下文中提出 并在该上下文中得到回答 但没有明确说明有关 C 11 的内容 const some type create const thingy lock my lock some mutex static con
  • 转到 C# WPF 中的第一页

    我正在 WPF 中使用导航服务 为了导航到页面 我使用 this NavigationService Navigate new MyPage 为了返回我使用 this NavigationService GoBack 但是如何在不使用的情况
  • 为什么 std::allocator 在 C++17 中丢失成员类型/函数?

    一边看着std 分配器 http en cppreference com w cpp memory allocator 我看到成员 value type pointer const pointer reference const refer
  • 禁用 LINQ 上下文的所有延迟加载或强制预先加载

    我有一个文档生成器 目前包含约 200 个项目的查询 但完成后可能会超过 500 个 我最近注意到一些映射表示延迟加载 这给文档生成器带来了一个问题 因为它需要根据生成的文档来访问所有这些属性 虽然我知道DataLoadOptions可以指
  • 组合框项目为空但数据源已满

    将列表绑定到组合框后 其 dataSource Count 为 5 但组合框项目计数为 0 怎么会这样 我习惯了 Web 编程 而且这是在 Windows 窗体中进行的 所以不行combo DataBind 方法存在 这里的问题是 我试图以
  • C# 编译器如何决定发出可重定向的程序集引用?

    NET Compact Framework 引入了可重定向程序集引用 现在用于支持可移植类库 基本上 编译器会发出以下 MSIL assembly extern retargetable mscorlib publickeytoken 7C
  • std::bind 重载解析

    下面的代码工作正常 include
  • 如何检测 C# 中该字典键是否存在?

    我正在使用 Exchange Web 服务托管 API 和联系人数据 我有以下代码 即功能性的 但并不理想 foreach Contact c in contactList string openItemUrl https service
  • Fluent NHibernate 日期时间 UTC

    我想创建一个流畅的 nhibernate 映射来通过以下方式映射 DateTime 字段 保存时 保存 UTC 值 读取时 调整为本地时区值 实现此映射的最佳方法是什么 就我个人而言 我会将日期存储在 UTC 格式的对象中 然后在读 写时在
  • 如何在 GCC 5 中处理双 ABI?

    我尝试了解如何克服 GCC 5 中引入的双重 ABI 的问题 但是 我没能做到 这是一个重现错误的非常简单的示例 我使用的GCC版本是5 2 如您所见 我的主要函数 在 main cpp 文件中 非常简单 main cpp include
  • 热重载时调用方法

    我正在使用 Visual Studio 2022 和 C 制作游戏 我想知道当您热重新加载应用程序 当它正在运行时 时是否可以触发一些代码 我基本上有 2 个名为 UnloadLevel 和 LoadLevel 的方法 我想在热重载时执行它
  • Swagger 为 ASP.CORE 3 中的字典生成错误的 URL

    当从查询字符串中提取的模型将字典作为其属性之一时 Swagger 会生成不正确的 URL 如何告诉 Swagger 更改 URL 中字典的格式或手动定义输入参数模式而不自动生成 尝试使用 Swashbuckle 和 NSwag 控制器 pu
  • WPF/数据集:如何通过 XAML 将相关表中的数据绑定到数据网格列中?

    我正在使用 WPF DataSet 连接到 SQL Server Express XAML 和 C Visual Studio 2013 Express 我从名为 BankNoteBook 的现有 SQL Server Express 数据
  • 如何创建向后兼容 Windows 7 的缩放和尺寸更改每显示器 DPI 感知应用程序?

    我是 WPF 和 DPI 感知 API 的新手 正在编写一个在 Windows 7 8 1 和 10 中运行的应用程序 我使用具有不同每个显示器 DPI 设置的多个显示器 并且有兴趣将我的应用程序制作为跨桌面配置尽可能兼容 我已经知道可以将

随机推荐