3

对不起,文字墙的人,但这需要解释,太多的代码要发布......

我正在将固定宽度的文件导入到需要数据输入的方法中。我使用 transferText 将文件导入两个规范(一个是全局的,另一个是特殊情况)。

我有一个函数,它使用 DAO 循环遍历 TableDefs 中的所有 Field 对象,以构建一个包含 AutoIncrement PK 的重复表,因此我能够编辑这些记录。我使用 INSERT INTO 将数据推送到该表中。

效果很好。发现错误后,用户进入数据输入以手动更正错误,这比筛选 400 个字符行并按照应有的方式重新组织所有内容要好。效果很好!

问题:当数据输入发生更改时,按下提交按钮,该按钮调用表单外模块内的函数。它关闭数据输入表单并将信息推送回原始表减去自动递增的 PK,并且应该删除带有 ID 的复制表,并再次生成一个新表以查找错误...

它推回原来的就好了,但它不会删除 ID 表。总是返回给我一条消息,表明此表已锁定。我注意到该表被无限锁定,直到所有函数/子退出。在任何时候单步执行代码时,我都无法手动删除它,一旦执行完成,我就可以将其删除。

我假设由于我通过表单中的命令调用它,因此在所有代码完成并且可以调用表单终止并执行其操作之前,不会释放锁。有什么想法吗?是的,这很野蛮,但效果很好,我只需要能够将这张桌子从地球上撕下来,这样我就可以重新删除更新的副本......

在最坏的情况下,我可以让用户关闭表单并点击主表单中的另一个按钮,但这是在考虑用户竞争力的情况下设计的。然而,这现在引起了我的全部关注,并且希望至少找到一个解决方案,即使它不是最佳解决方案。

-编辑-

在这个问题中使用了两种形式

FormA (Role: Load in and search for problems)

Examine button is pressed that:

 - Uses TextTransfer based on predefined specs into tempExtract to
       import the file

 - DAO fires off on the Fields collection in tableDefs for
   tempExtract, creates new table tempExtractID

 - Performs searches through the file to find errors.  Errors are saved to
   a table Problem_t.  Table contains Problem_ID (Set from the ID field
   added to tempExtractID) and Description

 - Execution of these tasks is successfully requerying the initial
   form to showing a list of problems and number of occurances.  A button
   gains visibility, with onClick that opens the form DataEntry.            

 - At this point in the code after DAO execution, I can DROP the table
   tempExtractID.  DAO is NOT used again and was only used to build a new table.

表格B - 数据输入表格

一旦我打开此表单,表 tempExtractID 就会被锁定,我无法删除该表。表单的记录源根据 Problems_t 中的 ID 查询 tempExtractID,以仅返回我们需要键入的内容。

在表格完全终止之前,我不能放下表格。按下数据输入表单上的按钮以提交更改,其中只有5行代码可以在我收到锁定错误之前触发。

*Xargs refers to the list of Field names pulled earlier through DAO.  As DAO loops through Field objects, the physical names are added to an Xargs String which is placed in this table.  Basically everything but the AutoNumber is being inserted back

    docmd.Close acForm, "frmDataEntry", acSaveNo
    call reInitializeExtract
         > docmd.RunSQL "DELETE FROM tempExtract"
         > docmd.RunSQL "INSERT INTO tempExtract SELECT (" & DLookup("Value", "CONFIG_t", "Item = 'Xargs'" & ") FROM tempExtractID"
    docmd.DeleteObject acTable, "tempExtractID"

这是在打开表单(表第一次被锁定的位置)和继续锁定直到所有子和功能完成之间运行的唯一代码。

4

2 回答 2

6

我建议将表单的记录源设置为 vbNullString,然后删除表。这应该有效,除非您还有组合框等绑定到此表。

于 2009-10-30T11:03:38.960 回答
4

没有代码很难说,但如果你使用的是 DAO,你需要清理你的代码对象。这意味着将您的数据库对象设置为 Nothing,并关闭任何记录集对象并将其设置为 Nothing。

  Dim db As DAO.Database
  Dim rs As DAO.Recordset

  Set db = DBEngine.OpenDatabase("[path to database]")
  Set rs = db.OpenRecordset("[SELECT statement]")
  rs.Close
  Set rs = Nothing
  db.Execute("[DML or DDL statement]", dbFailOnError)
  db.Close
  Set db = Nothing

  Set db =CurrentDB
  Set rs = db.OpenRecordset("[SELECT statement]")
  rs.Close
  Set rs = Nothing
  Set db = Nothing  ' you don't close a db variable initialized with CurrentDB

虽然 VBA 应该在这些对象超出范围时清理它们,但它并不是 100% 可靠的(因为 VBA 使用引用计数来跟踪对象是否可以被释放,并且它并不总是知道所有引用何时都有被清除)。

保持打开状态的对象最有可能是锁的来源,因此您应该确保在使用完对象变量后清理它们。

在看到您使用 DoCmd.RunSQL 后进行编辑:

使用 DoCmd.RunSQL 可能是问题的原因。这肯定会剥夺您对连接的程序化管理。如果您改用 DAO,您将可以控制连接,并避免 DoCmd.RunSQL 的真正缺陷,即它不处理错误。如果一个 DML 或 DDL 语句不能完全成功地完成,那么整个事情就应该失败。例如,如果您要附加 100 条记录,其中 10 条因密钥违规而失败,DoCmd.RunSQL 将透明地附加 90 条记录,而不报告 10 条失败记录。更新和任何其他 DML/DDL 语句也是如此。DoCmd.RunSQL “有用地”默默地完成了尽可能多的更新,让您不知道其中一些更新未能完成。

当然,在某些情况下,您可能希望这种情况发生,例如,如果您要附加您知道可能有 PK 冲突的记录,并且不想将 CPU 周期花费在消除记录集中重复的外部联接上你在追加。

但大多数时候,情况并非如此。

正如我在上面的评论中所说,我使用了一个旨在透明地替换 DoCmd.RunSQL 并使用 DAO Execute 语句和错误处理的函数。我已经在 SO 上发布了几次(这里是一个),这是我在当前最活跃的开发项目中用于生产的版本:

  Public Function SQLRun(strSQL As String, Optional db As Database, _
       Optional lngRecordsAffected As Long) As Long
  On Error GoTo errHandler
    Dim bolCleanup As Boolean

    If db Is Nothing Then
       Set db = CurrentDb
       bolCleanup = True
    End If
    'DBEngine.Workspaces(0).BeginTrans
    db.Execute strSQL, dbFailOnError
    lngRecordsAffected = db.RecordsAffected
    'DBEngine.Workspaces(0).CommitTrans

  exitRoutine:
    If bolCleanup Then
       Set db = Nothing
    End If
    SQLRun = lngRecordsAffected
    'Debug.Print strSQL
    Exit Function

  errHandler:
    MsgBox "There was an error executing your SQL string: " _
       & vbCrLf & vbCrLf & Err.Number & ": " & Err.Description, _
       vbExclamation, "Error in SQLRun()"
    Debug.Print "SQL Error: " & strSQL
    'DBEngine.Workspaces(0).Rollback
    Resume exitRoutine
  End Function

(这些事务被注释掉,因为它们引起了我没有时间解决的问题)

你可以替换你的这些行:

  DoCmd.RunSQL "DELETE FROM tempExtract"
  DoCmd.RunSQL "INSERT INTO tempExtract SELECT (" _
    & DLookup("Value", "CONFIG_t", "Item = 'Xargs'" & ") FROM tempExtractID"

...有了这个:

  SQLRun "DELETE FROM tempExtract"
  SQLRun "INSERT INTO tempExtract SELECT (" _
    & DLookup("Value", "CONFIG_t", "Item = 'Xargs'" & ") FROM tempExtractID"

你也可以这样做:

  Debug.Print SQLRun("DELETE FROM tempExtract") & " records deleted."
  Debug.Print SQLRun("INSERT INTO tempExtract SELECT (" _
    & DLookup("Value", "CONFIG_t", "Item = 'Xargs'" _
    & ") FROM tempExtractID") & " records inserted."

由于该函数为每个执行返回 .RecordsAffected,您可以打印到即时窗口,或者您可以将返回值分配给变量,或者将现有变量传递给它并使用该变量:

  Dim lngRecordsAffected As Long
  ...
  Call SQLRun("DELETE FROM tempExtract", , lngRecordsAffected)
  Debug.Print lngRecordsAffected & " records deleted."
  Call SQLRun("INSERT INTO tempExtract SELECT (" _
    & DLookup("Value", "CONFIG_t", "Item = 'Xargs'" _
    & ") FROM tempExtractID", , lngRecordsAffected)
  Debug.Print lngRecordsAffected & " records inserted."

关键是如果 Execute 语句有错误,整个事情都会失败(并弹出一条错误消息 - 你可能想要更改它,以便如果有错误它返回 -1 或类似的而不是弹出一个消息框)。

我最常通过传入一个预缓存的数据库变量来使用这个函数,所以我不想在之后清理它。如果您使用 CurrentDB() 以外的其他数据库,您确实希望确保任何指向您的外部数据库的数据库变量都已关闭并设置为 Nothing。否则,顶级数据库对象上的锁将保持不变,LDB 文件将保持打开和活动状态。

于 2009-10-30T03:06:35.243 回答