为什么我的嵌套关联没有加载?

Why isn't my nested association loading?

我有以下模型:Mainline -> Release -> Overlay。我正在尝试在一个急切加载的查询中获取主线的所有叠加层。

(这是一个最初在 SQL 服务器上使用 Entity Framework 启动的数据库,所以我有一个额外的并发症,即密钥不是 Rails-标准。)

mainline.rb

class Mainline < ActiveRecord::Base
  self.primary_key = 'MainlineID'
  has_many :releases, foreign_key: 'Mainline_MainlineID'
  has_many :overlays, through: :releases
end

release.rb

class Release < ActiveRecord::Base
  self.primary_key = 'ReleaseID'
  belongs_to :mainline, foreign_key: 'Mainline_MainlineID'
  has_many :overlays, foreign_key: 'Release_ReleaseID'
end

overlay.rb

class Overlay < ActiveRecord::Base
  self.primary_key = 'OverlayID'
  belongs_to :release, foreign_key: 'Release_ReleaseID'
end

我可以在 Rails 控制台中执行以下操作:

m = Mainline.where(MainlineID: 2025).includes(releases: :overlays).where.not(overlays: { Version: nil }).first

这给了我以下 SQL,这是正确的。我可以在 SQL Server Management Studio 中 运行 得到正确的结果。

  SQL (40.8ms)  EXEC sp_executesql N'SELECT DISTINCT TOP (1) [mainlines].[MainlineID] FROM [mainlines] LEFT OUTER JOIN [releases] ON [releases].[Mainline_MainlineID] = [mainlines].[MainlineID] LEFT OUTER JOIN [overlays] ON [overlays].[Release_ReleaseID] = [releases].[ReleaseID] WHERE [mainlines].[MainlineID] = 2025 AND ([overlays].[Version] IS NOT NULL) ORDER BY [mainlines].[MainlineID] ASC'
  SQL (138.0ms)  EXEC sp_executesql N'SELECT [mainlines].[MainlineID] AS t0_r0, [mainlines].[Program_ProgramID] AS t0_r1, [mainlines].[Designation] AS t0_r2, [mainlines].[Lifecycle] AS t0_r3, [releases].[ReleaseID] AS t1_r0, [releases].[Mainline_MainlineID] AS t1_r1, [releases].[SoftwareVersion] AS t1_r2, [releases].[ModuleName] AS t1_r3, [releases].[FirstProductConfigFileVersion] AS t1_r4, [releases].[ProductIdString] AS t1_r5, [releases].[ModulePartNumber] AS t1_r6, [releases].[InterfaceLevel] AS t1_r7, [releases].[CreationDate] AS t1_r8, [releases].[StartBootLoaderVersion] AS t1_r9, [releases].[EndBootLoaderVersion] AS t1_r10, [releases].[ByteOrder] AS t1_r11, [releases].[IndexTableAddress] AS t1_r12, [releases].[Description] AS t1_r13, [releases].[FileNameBase] AS t1_r14, [releases].[ImportedDate] AS t1_r15, [overlays].[OverlayID] AS t2_r0, [overlays].[Release_ReleaseID] AS t2_r1, [overlays].[Version] AS t2_r2, [overlays].[OverlayDate] AS t2_r3, [overlays].[Proposal_ProposalID] AS t2_r4, [overlays].[Lifecycle] AS t2_r5, [overlays].[Description] AS t2_r6, [overlays].[Comments] AS t2_r7, [overlays].[ImportedDate] AS t2_r8, [overlays].[Proposer] AS t2_r9 FROM [mainlines] LEFT OUTER JOIN [releases] ON [releases].[Mainline_MainlineID] = [mainlines].[MainlineID] LEFT OUTER JOIN [overlays] ON [overlays].[Release_ReleaseID] = [releases].[ReleaseID] WHERE [mainlines].[MainlineID] = 2025 AND ([overlays].[Version] IS NOT NULL) AND [mainlines].[MainlineID] IN (2025) ORDER BY [mainlines].[MainlineID] ASC

我的问题是我可以在不再次访问数据库的情况下循环发布...

2.2.3 :137 > m.releases
 => #<ActiveRecord::Associations::CollectionProxy [#<Release ReleaseID: 126, Mainline_MainlineID: 2025, SoftwareVersion: "40190004_0", ModuleName: "CM23xx", FirstProductConfigFileVersion: "2003.1.1.0", ProductIdString: "BHQ", ModulePartNumber: "9999999", InterfaceLevel: "4.6.0.0", CreationDate: "2015-06-16 00:0

但不是叠加层...

2.2.3 :138 > m.overlays
  Overlay Load (78.8ms)  EXEC sp_executesql N'SELECT [overlays].* FROM [overlays] INNER JOIN [releases] ON [overlays].[Release_ReleaseID] = [releases].[ReleaseID] WHERE [releases].[Mainline_MainlineID] = @0', N'@0 int', @0 = 2025  [["Mainline_MainlineID", 2025]]
 => #<ActiveRecord::Associations::CollectionProxy [#<Overlay OverlayID: 3747, Release_ReleaseID: 126, Version: 86, OverlayDate: "2015-07-30 00:00:00", Proposal_ProposalID: nil, Lifecycle: "Imported", Description: nil, Comments: nil, ImportedDate: "2015-08-24 13:59:44", Proposer: nil>, #<Overl

它正在生成另一个查询,这次没有我的条件 Version 不为 NULL。为什么 Rails 没有意识到我已经有了我想要的叠加层,包括我选择的主线?

更新:最初发布后,我尝试了以下操作:

m = Mainline.includes(:releases, :overlays).where.not(overlays: { Version: nil }).find(2025)

请注意 .includes() 中的更改。这给了我想在控制台中看到的内容:

2.2.3 :066 > m.overlays
 => #<ActiveRecord::Associations::CollectionProxy [#<Overlay OverlayID: 3747, Release_ReleaseID: 126, Version: 86, OverlayDate: "2015-07-30 00:00:00", Proposal_ProposalID: nil, Lifecycle: "Imported", Description: nil, Comments: nil, ImportedDate: "2015-08-24 13:59:44", Proposer: n

这会产生很大的不同 SQL:

  SQL (657.4ms)  EXEC sp_executesql N'SELECT [mainlines].[MainlineID] AS t0_r0, [mainlines].[Program_ProgramID] AS t0_r1, [mainlines].[Designation] AS t0_r2, [mainlines].[Lifecycle] AS t0_r3, [releases].[ReleaseID] AS t1_r0, [releases].[Mainline_MainlineID] AS t1_r1, [releases].[SoftwareVersion] AS t1_r2, [releases].[ModuleName] AS t1_r3, [releases].[FirstProductConfigFileVersion] AS t1_r4, [releases].[ProductIdString] AS t1_r5, [releases].[ModulePartNumber] AS t1_r6, [releases].[InterfaceLevel] AS t1_r7, [releases].[CreationDate] AS t1_r8, [releases].[StartBootLoaderVersion] AS t1_r9, [releases].[EndBootLoaderVersion] AS t1_r10, [releases].[ByteOrder] AS t1_r11, [releases].[IndexTableAddress] AS t1_r12, [releases].[Description] AS t1_r13, [releases].[FileNameBase] AS t1_r14, [releases].[ImportedDate] AS t1_r15, [overlays].[OverlayID] AS t2_r0, [overlays].[Release_ReleaseID] AS t2_r1, [overlays].[Version] AS t2_r2, [overlays].[OverlayDate] AS t2_r3, [overlays].[Proposal_ProposalID] AS t2_r4, [overlays].[Lifecycle] AS t2_r5, [overlays].[Description] AS t2_r6, [overlays].[Comments] AS t2_r7, [overlays].[ImportedDate] AS t2_r8, [overlays].[Proposer] AS t2_r9 FROM [mainlines] LEFT OUTER JOIN [releases] ON [releases].[Mainline_MainlineID] = [mainlines].[MainlineID] LEFT OUTER JOIN [releases] [releases_mainlines_join] ON [releases_mainlines_join].[Mainline_MainlineID] = [mainlines].[MainlineID] LEFT OUTER JOIN [overlays] ON [overlays].[Release_ReleaseID] = [releases_mainlines_join].[ReleaseID] WHERE ([overlays].[Version] IS NOT NULL) AND [mainlines].[MainlineID] = @0 AND [mainlines].[MainlineID] IN (2025)', N'@0 int', @0 = 2025  [["MainlineID", 2025]]

所以现在我有两个问题。首先,我不明白为什么 .includes() 语法有效,当我读到的关于这个主题的所有内容让我认为它真的应该是 (releases: :overlays),而不是逗号分隔的。

其次,更重要的是,我的目标是让 2 个以上的关联预加载,并且它们变得更毛茸茸。如果我无法理解第一个示例,就无法加载其他示例。我最终需要执行以下操作:

Mainline -> Release -> Overlay -> Calibration <- Parameter

我如何以正确的 hash/array 安排使用 .includes() 加载所有这些数据?从我读过的内容来看,我希望 .includes(release: [overlay: [calibration: :parameter]]),但我没有尝试过让我更接近。只是尝试添加校准级别让我得到整个数据库的叉积。

这只是我需要从单个查询中获取的核心数据集。我还需要结合用户、所有权和类别等。我的核心 "mainline" 有大约 17K 行,涉及大约 8 个不同的模型。对于此应用程序,我绝对负担不起任何类型的 N+1 查询。我必须一口气搞定。 (我已经成功地将它设置为 find_by_sql(),它准确地获取了我需要的数据,但我想让它继续运行 "the Rails way,",以便我可以按预期遍历关系。)

我试过使用 .joins(),因为它可以实现 INNER JOINS,这是我真正需要的,但它似乎没有预加载数据。我什至尝试过结合 .joins() 和 .includes() ,正如我看到有些人所做的那样,但现在我只是在猜测。我不明白为什么我在第一遍中尝试的方法不起作用,而且在我这样做之前,我只是在浪费时间。

我现在明白我误解了如何引用预先加载的数据。尽管使用 has_many through: 进行了正确设置,但您无法跳过第二个关联并直接访问急切加载数据中的第三个关联。如果不引发另一个查询,我将永远无法引用 @mainline.overlays 的成员。我必须将它们引用为 @mainline.releases[0].overlays[0].

令人宽慰的消息是,我对包含的语法并不疯狂。我只是错误地使用了结果。但是,基于这种访问数据的方式,我改变了查询,使我真正感兴趣的(校准)成为顶级查询,如下所示:

cals = Calibration
  .joins(overlay: [release: :mainline])
  .where(mainlines: { MainlineID: params[:id] })
  .where.not(overlays: { Version: nil })
  .includes(:parameter, overlay: [release: :mainline])

然后我可以遍历 cals 并访问参数数据,这是我的第一个 step/concern。

我现在还看到 .joins() 在根据关联值限制我的数据集方面的作用。与我的 .find_by_sql() 方法相比,我当前(有限的)结果集以这种方式需要 3 秒,后者只需要大约一半,并且已经包含了更多我尚未包含的模型,所以我不不知道这是否会很好地工作,但至少我明白现在发生了什么。

(当然,这两种方法都有一个问题,一旦 ~20K 行数据从浏览器中移出,浏览器需要 10 秒才能生成 table,但这是另一种颜色的马。 )