5

将单元格从 Excel 复制/粘贴到 Access 子窗体中时,如果没有创建父记录,将导致错误 - 正如预期的那样。

问题在于,发生此错误后,访问被锁定在输入的所有后续数据导致事务错误中不支持的操作的状态。当您打开表格时,您可以看到新添加的数据尚未在表格中 - 因此看起来好像 Access 实际上正在进行某种事务。

我试过点击“保存”..“刷新”..甚至添加一个 AfterInsert 事件来强制执行 commitTrans() 但没有运气 - Access 声称没有正在进行的交易

如果记录是手动输入的,则不会出错。该问题似乎仅在粘贴记录时出现。我猜测 Access 正在创建一个事务来处理多个记录操作,并且没有从错误中正确恢复。

我可以删除“必需”标志,它会起作用 - 但是你有孤儿记录。我在想也许可以使用 After Insert Data Macro 添加一个具有默认批次名称的新批次,并将新的 BatchID 自动填充到 Items 表中。但是,我不确定如何做到这一点。

我确实尝试使用更改前数据宏删除“必需”标志并捕获错误 - 但是虽然它减少了错误 - 它仍然产生了相同的操作不支持事务错误。

我在 Microsoft 知识库中查找了该错误,但没有找到任何特定于我的情况的内容。我在 stackoverflow 中搜索了错误消息,但一无所获。

我创建了一个新数据库并能够复制该问题。

复制步骤

设置数据库

  1. 在 Access 2010 中创建一个新的 ACCDB 数据库
  2. 创建一个名为“Batches”的表,其中包含以下字段:
    • BatchID(自动编号)(主键)
    • 批次名称(文本)
  3. 创建一个名为“Items”的表,其中包含以下字段:
    • RecordID(自动编号)(主键)
    • BatchID(长整数)
      • 必需属性设置为 True
    • 数据 - 文本
  4. 创建关系,将 Batches.BatchID 链接到 Items.BatchID
    • 包括批次中的所有记录,以及项目中的匹配记录
    • 强制参照完整性
    • 级联更新/删除
  5. 创建一个名为“表格”的表格
    • 将记录源设置为批处理
    • 添加 BatchID 和批次名称文本框
    • 添加一个 SubForm/SubReport 控件
      • 将源对象设置为“Table.Items”
      • 将链接主字段设置为“BatchID”
      • 将链接子字段设置为“BatchID”
      • 设置“空主过滤器”=是
  6. 创建示例数据(使用表单)
    • 批量创建新记录。
      • 设置 BatchName = "测试"
    • 在项目中创建新记录。
      • 参考批记录。
      • 设置数据=“测试”

如您所见,手动操作很好。

从 Excel 复制和粘贴数据

  1. 在 Excel 中
    • 从 A1-A10 向下每个单元格输入一个字母:A、B、C、D、E、F、G、H、I、J
    • 突出显示单元格 A1-A10
    • 复制(Control+C)
  2. 在 Access 中,使用表单:
    • 添加新的批次记录。它应该在 BatchID TextBox 中显示“(新)”
    • 不要输入批次名称
    • 在子表单中,单击新记录的记录选择器 (*) 以选择整行。键入 Control+V 进行粘贴。
    • 单击“您必须在 'Data.BatchID' 字段中输入一个值。错误
      • 此错误可能会重复。继续点击确定
      • 如果它询问“你想抑制更多的错误消息......”回答是
    • 单击“确定”以查看“Microsoft Access 无法粘贴的记录已插入到名为“粘贴错误”的新表中。通知
    • 用“TestName”填写批次名称文本框
    • 尝试优雅地恢复。点击逃生。更改记录。

此时 - 您应该看到输入的 BatchID、批次名称和新数据。一切似乎都按预期工作。如果您尝试刷新或导航到另一个批次记录 - 您将收到错误Operation not supported in transactions。Access 将继续显示此错误消息,直到我们关闭并重新打开表单。您粘贴的数据不会进入数据库。

通常,具有一点技术知识的人会意识到某些事情进展不顺利并关闭数据库并重新打开......但不幸的是,我有用户使用任何弹出框玩“whack-a-mole”然后尝试继续 - 所以我想让事情尽可能地防弹。

所需的解决方案

我想要一个解决这个问题的方法,它最终不会导致访问、重复值等其他怪癖。

以我自己的经验,使用 VBA 来“修复”密钥是不可靠的。数据宏似乎更可靠-但设置起来可能很棘手-它们还不是很主流(我想说stackoverflow上应该有一个ms-access-data-macros标签,但没有然而)

4

5 回答 5

1

Suggested workaround:

In the [Batches] table, set the Required property of the [BatchName] field to Yes.

Change the Tab Stop property of the [BatchID] text box to "No". That will give the [BatchName] text box the default focus when the form opens.

Have the On Current event of the form give the [BatchName] text box the focus for new records (IsNull(Me.BatchID) = True).

Make the form dirty when the [BatchName] text box loses focus.

Option Compare Database
Option Explicit

Dim DeletePending As Boolean

Private Sub Form_Load()
    DeletePending = False
    Me.ItemSubForm.Enabled = False  ' Disable Subform by default
End Sub

Private Sub Form_Current()
    If IsNull(Me.BatchID) Then
        Me.BatchName.SetFocus
        ' Disable Subform if BatchID is NULL
        Me.ItemSubForm.Enabled = False
    Else
        ' Enable SubForm Otherwise
        Me.ItemSubForm.Enabled = False 
    End If
End Sub

Private Sub Form_BeforeDelConfirm(Cancel As Integer, Response As Integer)
    DeletePending = True
End Sub

Private Sub Form_AfterDelConfirm(Status As Integer)
    DeletePending = False
End Sub

Private Sub BatchName_LostFocus()
    If IsNull(Me.BatchID) And Not DeletePending Then
        Me.Dirty = True
    End If
End Sub

When the user clicks on the subform (and off the [BatchName] text box) they make the form dirty and BatchID gets a value. Then they can paste and they don't get the "You must enter a value..." message for [BatchID]. If they haven't entered a [BatchName] value they now get prompted for it (because it is now Required), but at least they can gracefully recover from that.

Updated 2013-11-09 18:40 UTC:

In playing around with this a bit more I discovered two quirks:

  1. If you deleted the last parent record Access would do it and then immediately create another new one in its place.

  2. If you navigated into the "new" parent record and then immediately backed out (e.g., via the PageDown and PageUp keys) Access would create a new record and then leave you on that record with the form dirty. Hitting Esc and then moving off the "new" record worked, and it didn't cause any errors, but it certainly could be confusing to the user.

I have updated the answer to try and address these issues. I added a bit more VBA code to track "DeletePending" status. I also added the requirement that [BatchName] be set to Required = Yes in the [Batches] table. The latter makes it slightly more intuitive (albeit slightly more annoying) if the user move into the "new" record and then immediately moves back out again.

于 2013-11-09T15:03:37.250 回答
1

我为此苦苦挣扎了很长时间,直到我终于明白是什么导致了这个错误。详细介绍需要一篇相当长的文章,而不是博客回复来解释这一切。如果有人有兴趣可以留下联系方式,我会联系他们并详细说明。但是,对于那些想弄清楚这一点的人,我可以通过给您提供问题背后的想法来为您节省大量时间:当您在绑定的子表单中执行数据事务时,您不能引用其他对象。交易流程的内部代码不允许这样做。例如,如果您在 Form_BeforeUpdate 事件中有代码尝试在子表单数据事务中打开另一个表单,您将收到错误 3246。您可以拥有创建变量、设置值、引用子表单控件等,但您不能转到另一个对象。如果你仔细想想,这是有道理的。一旦用户或代码进入另一个表单或对象,谁知道它会做什么。它可能永远不会回来或卷入导致交易挂起的其他错误。这就是交易必须首先完成的原因。这意味着您必须捕获并消除错误 2115,该错误是由于用户在事务中尝试单击子表单之外的区域而导致的。这通常发生在大型复制和粘贴期间,其中用户在子表单事务中变得住院或开始继续到另一条记录。一旦用户或代码进入另一个表单或对象,谁知道它会做什么。它可能永远不会回来或卷入导致交易挂起的其他错误。这就是交易必须首先完成的原因。这意味着您必须捕获并消除错误 2115,该错误是由于用户在事务中尝试单击子表单之外的区域而导致的。这通常发生在大型复制和粘贴期间,其中用户在子表单事务中变得住院或开始继续到另一条记录。一旦用户或代码进入另一个表单或对象,谁知道它会做什么。它可能永远不会回来或卷入导致交易挂起的其他错误。这就是交易必须首先完成的原因。这意味着您必须捕获并消除错误 2115,该错误是由于用户在事务中尝试单击子表单之外的区域而导致的。这通常发生在大型复制和粘贴期间,其中用户在子表单事务中变得住院或开始继续到另一条记录。这意味着您必须捕获并消除错误 2115,该错误是由于用户在事务中尝试单击子表单之外的区域而导致的。这通常发生在大型复制和粘贴期间,其中用户在子表单事务中变得住院或开始继续到另一条记录。这意味着您必须捕获并消除错误 2115,该错误是由于用户在事务中尝试单击子表单之外的区域而导致的。这通常发生在大型复制和粘贴期间,其中用户在子表单事务中变得住院或开始继续到另一条记录。

于 2014-05-03T02:02:51.280 回答
1

我知道这是一个古老的故事,我也在为此苦苦挣扎。我的解决方案是重新设计流程,以便用户关闭接收数据的表单以保存插入的记录。既不优雅但高效,让我免于猜测可能发生的每一个事件。

于 2014-09-16T07:17:42.620 回答
0

避免不希望的内部事务足以将子表单错误事件编码为:

Private Sub Form_Error(DataErr As Integer, Response As Integer)

    Response = acDataErrContinue

End Sub

拦截子表单事件的一般方法是

' parent form code
' ---------------------------------------------------
Private WithEvents subFormObj As [Form_Sottomaschera items]

Private Sub Form_Open(Cancel As Integer)

    Set subFormObj = Me.Sottomaschera_items.Form

End Subcode here

' asynchronous way
Private Sub subFormObj_isInserting()

    Me.batchName = vbNullString         ' this resolves the new ID issue

End Sub

' synchronous way
Public Sub subFormInserting()

    Me.batchName = vbNullString

End Sub

' sub-form code
' ---------------------------------------------------
Public Event isInserting()              ' for asynchronous way

Dim parentFormObj As Form_Maschera1     ' for synchronous way

Private Sub Form_BeforeInsert(Cancel As Integer)

    parentFormObj.subFormInserting
    RaiseEvent isInserting
'    Cancel = True

End Sub

Private Sub Form_Error(DataErr As Integer, Response As Integer)

    Response = acDataErrContinue

End Sub

Private Sub Form_Open(Cancel As Integer)

    Set parentFormObj = Me.Parent

End Sub

[Maschera1]主窗体和[Sottomaschera items]子窗体在哪里。

不幸的是,它不能解决粘贴问题。

要明确解决问题,您需要保存父记录+一个SetFocus技巧,同步或异步:

Private Sub subFormObj_isInserting()

    Me.batchName = vbNullString
    DoCmd.RunCommand acCmdSaveRecord
'    DoEvents
    Me.batchName.SetFocus

End Sub

Public Sub subFormInserting()

    Me.batchName = vbNullString
    DoCmd.RunCommand acCmdSaveRecord
'   DoEvents
    Me.batchName.SetFocus

End Sub
于 2013-11-09T18:30:30.270 回答
0

我不明白你到底想达到什么目标,所以这个答案可能不够充分。你可以

  • 设置您的子表单属性.Visible = FalseMe.NewRecord = True以防止在其中输入数据
  • .Dirty = False通过在按 Enter 触发的更新后事件中设置添加批处理名称后,将主窗体的记录强制保存到表中。它还允许避免在向某些数据库中的主表单添加少量记录后不将子表单的记录保存到表中,至少对于动态子表单 .Recordsource。
  • 设置您的子表单属性.Visible = True

下面的代码适用于表单视图,也许应该以某种方式扩展(开发)其他视图。

将子窗体 Child 的 .Tag 和要隐藏/显示的所有其他控件设置为“a”。

Private Sub Form_Current()

If Me.CurrentView = 1 Then

    If Me.NewRecord = True Then
       ShowControls False
    ElseIf Me![Items subform Child].Visible = False Then
       ShowControls True
    End If

End If

End Sub

Private Sub BatchName_Text_AfterUpdate()

Dim NewRecordx As Boolean

If Me![Items subform Child].Visible = False And Me.CurrentView = 1 Then ShowControls True

NewRecordx = Me.NewRecord

If Me.Dirty Then Me.Dirty = False 'save the new record to the table

If Me.CurrentView = 1 And NewRecordx Then Me![Items subform Child].Form.Requery

End Sub

Private Sub ShowControls(bVisible As Boolean)

Dim ctl As Control

For Each ctl In Me.Controls
   If ctl.Tag = "a" Then ctl.Visible = bVisible
Next ctl

End Sub
于 2017-02-16T14:00:05.760 回答