即使在docmd.close之后,表格仍然锁定了我的表格

时间:2009-10-30 01:45:07

标签: ms-access locking create-table drop-table

对不起文字家伙的墙,但这个需要expliaining,太多的代码发布...

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

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

效果很好。发现错误,用户进入数据输入以手动纠正它们,这些过程通过筛选400个字符行并按照预期的方式重新组织所有内容。效果很好!

问题:当数据条目发生变化时,会按下一个提交按钮,该按钮调用表单外部模块内的函数。它关闭数据输入表单并将信息推回​​原始表减去自动增量PK,并且支持使用ID's删除复制表,并生成一个新的再次搜索错误...

它推回原来就好了,但它不会DROP 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.

FormB - 数据录入表格

一旦打开此表单,表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"

这是在打开表单的时间(表首先被锁定的位置)之间运行的唯一代码,并且继续被锁定直到所有子表和功能已经完成。

2 个答案:

答案 0 :(得分:6)

我建议将表单的记录源设置为vbNullString,然后删除表。这应该有效,除非你还有这个表格中的组合框等等。

答案 1 :(得分: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上发布了几次(here's one),这是我目前最活跃的开发项目中生产中使用的版本:

  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."

由于函数返回每个Execute的.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或其他一些错误消息弹出一个MsgBox)。

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