当子表单绑定到特定存储过程时,为什么我无法加载它?

Why am I unable to load a subform when it is bound to a specific stored procedure?

总结

在 Microsoft Access 2010 数据库 (accdb) 中,我有一个表单可以将其他表单动态加载到主表单上的子表单对象中。子窗体对象中使用的窗体绑定到 ODBC 传递查询,该查询对 return 记录集执行存储过程。我不明白为什么我可以使用一个存储过程并且它工作得很好,但是如果我将表单绑定到另一个存储过程,它就无法加载子表单。

技术演练

我有两个直通 ODBC 查询。 qryGoodqryBad。它们使用相同的 ODBC 连接字符串 (ODBC;DRIVER=SQL Server;SERVER=MyServer;UID=MyUser;Trusted_Connection=Yes;DATABASE=MyDatabase),它们后面的 SQL 是相同的,但指向 SQL 2012 数据库服务器上的两个不同的 SQL 存储过程。

qryGood 来源:exec spGoodProc 123456

qryBad 来源:exec spBadProc 123456

存储过程后面的SQL很简单。 Return 条来自单个 table 的记录,按作为参数传递的 ID 进行过滤。 (有些人会做更复杂的事情,但我在这里只关注一个演示问题的简化示例。)

frmMySubformRecordSource属性设置为qryBad

子表单 SourceObject 通过 VBA 代码设置:sfrmMain.SourceObject = "frmMySubform" 此时不会抛出任何错误。虽然 SourceObject 属性 现在 return 是 frmMySubform,但 .Form 对象似乎没有设置。

然后我尝试在子表单上引用 属性:Debug.Print sfrmMain.Form.Name 失败并出现错误 2467:您输入的表达式引用了一个已关闭或未关闭的对象'不存在。

然后我可以在设计视图中打开 frmMySubform,将 RecordSource 属性 更改为 qryGood,它工作正常。这似乎指向 spBadProc 的问题,只有在子表单上用作 RecordSource 时才会出现。

我试过的

为了解决这个问题,我使用排除法尽可能地缩小了范围,但我仍然不明白为什么一个存储过程有效而另一个存储过程无效。 return 记录在 SQL 和 运行 直接传递查询时都很好。直接打开表单时两者都可以正常工作。只有在子表单控件中将表单设置为 SourceObject 时才会出现问题。

我用sp_procedure_params_rowset比较了存储过程中的参数,它们是相同的。我比较了 SQL 中列的数据类型,tblBad 中没有任何新内容或不同之处,tblGood 中没有。我还尝试在设置表单时分析 SQL 服务器,它似乎可以很好地调用存储过程。比较坏调用和好调用之间的轨迹时,我没有看到任何线索。

RecordSet 直接设置为 ODBC link 到 tblBad 工作正常(并且我认为视图也可以)但是以某种方式具有简单的存储过程包装器触发错误。

我还比较了 spGoodProcspBadProc 的安全性、属性和扩展属性,它们是相同的。

我的问题

在故障排除方面我可以做些什么来进一步减少这种情况? 有没有人在子表单上遇到与绑定存储过程类似的问题?我正在处理一个非常复杂的数据库,其中包含数百个表单、tables 和查询,所以我真的很想在我走得太远之前了解为什么会发生这种情况。

提前感谢您就这个令人困惑的问题分享任何见解。 :-)

找到了!

在追踪到具有特定 table 的内容后,我删除了所有约束、键,然后从 table 的副本中删除了列,系统地测试我是否可以查明问题。果然是存储过程中特定的列名

只需将此列别名为其他名称即可解决问题。 (详见下文)

进一步测试后更新

在进一步测试以进一步查明问题之后,我想我现在明白了为什么会发生这种情况。 当您 link ODBC table 并指定唯一(键)列时,Access 将 automatically 尝试设置 LinkMasterFieldsLinkChildFields 在加载子表单时添加到键列名称,并且子表单具有同名的列。 虽然这适用于 linked tables or views, 当子窗体的RecordSource设置为时不起作用存储过程.

如果您尝试通过手动添加子表单来执行此操作,您将看到以下通知:

但是,如果您通过 VBA 代码设置子表单目标,则不会收到任何警告或错误消息。它根本不(完全)加载子表单。 @Albert D. Kallal,你说得对,这与 master/child 字段问题有关!

我能够在 Access 2010 和 Access 2016 的测试数据库文件中一致地重现该问题。如果您想亲眼看看,可以使用以下步骤重现它:

  1. 使用 PrimaryID 列创建 SQL table。
CREATE TABLE [dbo].[tblBugTest](
    [PrimaryID] [int] NOT NULL,
    [TestColumn] [nchar](10) NULL
) ON [PRIMARY]
  1. 将几个测试记录添加到您刚刚创建的 table。
  2. 为 return 记录创建一个存储过程。
CREATE PROCEDURE [dbo].[spBugExample]
AS SELECT * FROM tblBugTest
  1. 创建一个空白的 Microsoft Access 数据库 (accdb)。
  2. 使用 ODBC,创建 linked table 到 tblBugTest
    • 重要提示:Select PrimaryID 作为唯一列。
  3. 使用 ODBC 创建一个名为 qryPassThrough 的传递查询到同一个数据库,并将 SQL 设置为 exec spBugExample
    • 打开查询以验证它 returns 记录。
  4. 在数据库中创建三个空白表单。 frmMainfrmSubFormfrmBlank
  5. frmBlank 添加为 frmMain 的子表单。将子窗体命名为 sfrmSubform.
  6. frmMainRecordSource 设置为 linked table.
  7. frmMain 添加一个按钮以将子表单从 frmBlank 切换到 frmSubForm
Private Sub cmdShowBug_Click()
    With Me.sfrmSubform
        .SourceObject = "frmSubForm"
        Debug.Print .LinkMasterFields
        Debug.Print .LinkChildFields
        Debug.Print .Form.Name
        .SourceObject = "frmBlank"
    End With
End Sub
  1. frmSubForm 的 RecordSource 设置为 qryPassThrough
  2. 将几个绑定控件拖放到 frmSubForm 上。
    • 自己测试frmSubForm。它应该从 tblBugTest.
    • 加载一条记录
  3. 打开 frmMain 并单击按钮。它应该会引发错误。

如果您单步执行代码,您会看到在设置 SourceObject 之前,LinkMasterFields 属性 是空白的。设置好SourceObject后,可以将鼠标悬停在LinkMasterFields上,可以看到现在已经设置到PrimaryID列了。

解决方法

更改以下任何一项都可以通过避免 master/child 字段的自动 linking 问题来解决错误。

  • 删除并重新link linked table,这次不指定唯一列。
  • 将存储过程中的列别名为与唯一列不同的名称。
  • 清除父窗体的 RecordSource 属性。
  • 清除子表单 RecordSource 属性 并在 加载子表单后设置 RecordSet
  • 在子表单中使用视图或 linked table 而不是存储过程。

请记住,在加载主窗体之前将尝试加载子窗体数据源。这表明在主窗体的加载事件中,您将

首先设置 PT 查询。 然后设置子窗体的OBJECT源。

也就是说子窗体控件的源对象应该是空白的

您设置 PT 查询的代码将是:

With currentdb.queryDefs("qryGood")
   .SQL = "EXEC spGoodProc " & 123456
end with

当然,您可以将 123456 替换为变量,甚至是文本框中的值(来自主窗体)。

现在 PT 查询已设置,您接下来要设置要加载子表单的表单。

所以,在上面的代码之后,我们有:

me.mySubForm.SourceObject = "name of subform goes here"

所以,总共应该是4行代码左右。如上所示,您的 VBA 代码中甚至不需要任何连接字符串。

所以,请记住: 按照上述设置 PT 查询。然后您可以启动一个报表,甚至是一个表单,或者在这种情况下设置子表单控件要加载的表单。这也suggests/hints您需要删除子表单控件的源对象(留空)。

您可以设置来自源对象的子对象,但这会建议您在使用基于 PT 查询的子表单启动主表单之前按照上述设置 PT 查询源。如前所述,这组步骤是必需的,因为子表单实际上在主表单显示和呈现之前加载并解析它的数据源。因此,通过将子表单的源对象留空,开发人员可以重新获得对加载顺序的完全控制。