Entity Framework - 如何高效地预加载具有长依赖对象链的数据?

5
我是一名有用的助手,可以为您翻译文本。
我有一个问题,我认为许多专业开发人员都会遇到。我工作的地方采用了实体框架。我们使用它,并且很喜欢它。然而,我们似乎遇到了一个非常令人沮丧的限制。
假设您有一个对象链如下:
A->B->C->D
我们是专业人士,所以这些对象拥有大量数据,它们在各自的数据库表中也有很多。看起来 EF 加载超过 B 对象的任何内容时都会出现问题。它生成的 SQL 查询非常低效且不好。调用可能是这样的:
context.objects.include("bObjectName.cObjectName.dObjectName").FirstOrDefault(x => x.PK == somePK);

我们通过使用 .Load() 命令明确加载第二级别之后的对象来解决了这个问题。对于单个对象,这种方法很有效。但是,当涉及到对象集合时,我们开始遇到 .Load() 的问题。

首先,似乎没有办法在不使用 virtual 关键字的情况下保持对象集合的代理跟踪。这很有道理,因为它需要重写 get 和 set 函数。然而,这样会启用延迟加载,而 .Load() 不会在启用延迟加载时映射实体。我自己觉得这有点奇怪。如果您删除 virtual 关键字,则 .Load() 会自动将加载的对象链接到上下文中相关的对象。

因此,这就是我的问题所在。我想要代理跟踪,但也希望 .Load() 为我映射导航属性。如果 EF 可以生成良好的查询,所有这些都不会成为问题。我了解为什么它无法生成良好的查询,因为它必须是一次适用于所有情况的展示。

因此,为了加载第三层对象,我可以在我的服务层中创建一个加载函数,该函数获取第二层对象的所有主键,然后调用其中的一个 .Load()。

任何人有解决方案吗?EF7 或 Core 1.0 似乎通过以下方式解决了这个问题:

  1. 完全删除延迟加载,我们也可以关闭它,但这会破坏很多旧代码。
  2. 添加了一个新的 "ThenInclude" 功能,据说大大提高链接包含的效率。

如果关闭延迟加载是答案,那没关系,我只是想在浪费大量时间重新开发大量服务调用的 Web 应用程序之前尽可能地用完所有选项。

任何人有任何想法吗?我们正在使用 EF6。

编辑:看起来关闭上下文级别的延迟加载或升级到 EF7 是答案。如果有人找到一种在 EF6 中可以强制启用代理跟踪的方法并进行强制及时加载单个对象的解决方案,我将更改此内容。


你是否运行过 SQL Profiler 来查看实际的 SQL 查询是什么? - Ellesedil
是的,我可以告诉你,每当你链接到第三层时,查询就会变得非常低效。这是因为这些查询是过程性生成的,所以可以理解。尽管可以理解,但对于专业级软件来说并不起作用。一种解决方案是保存.Load()的结果,并手动将其设置为正确的对象。因此,在这种情况下,加载所有C并将它们设置为B,但我们这里的所有开发人员都同意,我们不喜欢那样做。我们喜欢移除虚拟功能时的功能,但我们也希望进行代理跟踪,以便我们可以添加到对象B中的icollection中。 - Gaugeforever
据我所知,你可以为EF 7的优势添加第三个要点。它最终实现了批量查询,这意味着它可以将许多查询捆绑在一个单独的SQL调用中。这应该允许它为依赖实体发出不同的查询,同时仍然导致单个SQL调用,而不是之前发出的庞大的单个查询。对于延迟加载的性能问题,这是由于缺乏批量延迟加载功能造成的EF特定问题。请参见此处有关NHibernate的解释。这将是你的一个改变游戏规则的机会。 - Frédéric
是的,我们使用控制台应用程序和EF7进行了一些测试,这显着提高了性能和查询生成,特别是在使用“ThenInclude”功能时。如果这就是答案,那就是答案了。我只是想看看是否还有其他解决方案,不需要进行太多更改。感谢您的回复。 - Gaugeforever
你所说的代理跟踪具体是什么意思?如果您能展示出代码,使其不按您所期望(或想要)的方式运行,那将会更清晰明了。我无法理解你怀疑的渴望和懒惰加载与跟踪之间的联系。我不知道你所说的*.Load()在启用懒惰加载时不映射实体是什么意思。Load()是一个渴望加载方法,它如何与懒惰加载相关呢?以及它如何映射*实体?实际上,我们需要的是代码。 - Gert Arnold
显示剩余2条评论
1个回答

1

关于链式.Include()语句,性能文档警告不要链接超过3个,因为每个.Include()都会添加一个外连接或联合。我不知道ThenInclude,但听起来像是一个革命性的变化!

如果保留虚拟导航属性但关闭DbContext上的延迟加载

 context.ObjectContext().ContextOptions.LazyLoadingEnabled = false;

如果启用了更改跟踪,那么您可以执行以下操作:

var a = context.aObjectNames.First(x=> x.id == whatever);

context.bObjectNames.Where(x=> x.aId == a.Id).Load()

这将填充a.bObjects。

您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Gaugeforever
您可以在不关闭上下文中的延迟加载的情况下进行急加载。只有当访问导航属性时才会加载,因此您可以提前进行急加载,以避免在访问属性时出现延迟加载。这真的取决于您的用例,但如果您只使用了 B 或 C 中的几个属性,那么惰性加载可能是有意义的,而不是拉回整个对象。显然,如果您正在遍历集合,则惰性加载并不是您的朋友,因为您最终会面临 n+1 问题。 - Tom Clelford
你可以在启用延迟加载的情况下,在上下文中进行急切加载。然而,对于长链集合,这会生成可怕的查询,并且在大量数据时非常缓慢。我认为这是一个只有在专业软件开发中才会出现的问题,因为它具有独特的限制。我们陷入了“做这个”、“但是那个”的循环论证中。EF7的“ThenInclude”功能解决了这个问题,但他们无论如何都删除了延迟加载。所以,是的...基本上,我需要急切加载,启用延迟加载,启用更改跟踪,以便我可以使用.Load()来让EF自动连接对象。 - Gaugeforever

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接