首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >MS (Jet)事务、工作区

MS (Jet)事务、工作区
EN

Stack Overflow用户
提问于 2010-05-25 01:17:53
回答 3查看 5.8K关注 0票数 4

我在提交事务(使用Access 2003 DAO)时遇到了问题。这就好像我从来没有调用过BeginTrans --我在CommitTrans上得到了错误3034,“您试图提交或回滚一个事务,而不首先开始一个事务”;这些更改被写入数据库(大概是因为它们从未包装在事务中)。但是,如果您执行BeginTrans,则会运行它。

  • 我使用DBEngine(0)工作区在Access环境中运行它。
  • --我要添加记录的表--都是通过Jet数据库连接(到同一个数据库)并使用DAO.Recordset.AddNew / Update打开的。在启动BeforeTrans.
  • I'm之前打开连接,在事务的中间不执行任何奇怪的操作,比如关闭/打开连接或多个工作区等。
  • 有两个嵌套的事务级别。基本上,它是在外部事务中包装多个插入,所以如果有任何失败,它们都会失败。内部事务运行时没有错误,不工作的是外部事务。

以下是我研究过并排除的几件事:

  • 事务是跨几个方法传播的,BeginTrans和CommitTrans (和回滚)都在不同的地方。但是,当我尝试以这种方式运行事务的简单测试时,这似乎并不重要。
  • 我认为,当数据库连接超出本地范围时,它可能会关闭,尽管我对它有另一个“全局”引用(老实说,我从来不知道DAO对dbase连接有什么作用)。但这似乎不是这种情况--就在提交之前,连接及其记录集是活动的(我可以检查它们的属性,EOF = False等),
  • --我的CommitTrans和回滚是在事件回调中完成的。(基本上:解析器程序在解析结束时抛出一个'onLoad‘事件,我正在处理这个事件,方法是提交或回滚我在处理过程中所做的插入,这取决于是否发生了错误。)然而,再次尝试一个简单的测试,这看起来并不重要。

知道为什么这不适合我吗?

谢谢。

编辑5月25日

以下是(简化的)代码。与交易有关的要点是:

工作空间是Set db = APPSESSION.connectionTo(dbname_).

  • BeginTrans (0),引用了公共(全局)变量的数据库连接,在下面的LoadProcess.cache中打开了行Set db = APPSESSION.connectionTo(dbname_).

  • BeginTrans是在LoadProcess.cache.

  • CommitTrans中调用的,在process__onLoad中调用的是在process__onLoad中调用的,在process__onInvalid中调用的是process__onLoadRow、logLoadInit和logLoad

埃里克

代码语言:javascript
复制
'------------------- 
'Application globals
'-------------------

Public APPSESSION As DAOSession

'------------------
' Class LoadProcess
'------------------

Private WithEvents process_ As EventedParser
Private errs_ As New Collection

Private dbname_ As String
Private rawtable_ As String
Private logtable_ As String
Private isInTrans_ As Integer

Private raw_ As DAO.Recordset
Private log_ As DAO.Recordset
Private logid_ As Variant

Public Sub run
    '--- pre-load
    cache
    resetOnRun    ' resets load state variables per run, omitted here
    logLoadInit
    Set process_ = New EventedParser

    '--- load
    process_.Load
End Sub

' raised once per load() if any row invalid
Public Sub process__onInvalid(filename As String)
    If isInTrans_ Then APPSESSION.Workspace.Rollback
End Sub

' raised once per load() if all rows valid, after load
Public Sub process__onLoad(filename As String)
    If errs_.Count > 0 Then
        logLoadFail filename, errs_
    Else
        logLoadOK filename
    End If

    If isInTrans_ Then APPSESSION.Workspace.CommitTrans
End Sub

' raised once per valid row
' append data to raw_ recordset
Public Sub process__onLoadRow(row As Dictionary)
On Error GoTo Err_

    If raw_ Is Nothing Then GoTo Exit_   
    DAOext.appendFromHash raw_, row, , APPSESSION.Workspace

Exit_:
    Exit Sub

Err_:
    ' runtime error handling done here, code omitted
    Resume Exit_

End Sub


Private Sub cache()
Dim db As DAO.Database

    ' TODO raise error
    If Len(dbname_) = 0 Then GoTo Exit_       
    Set db = APPSESSION.connectionTo(dbname_)
    ' TODO raise error
    If db Is Nothing Then GoTo Exit_ 

    Set raw_ = db.OpenRecordset(rawtable_), dbOpenDynaset)
    Set log_ = db.OpenRecordset(logtable_), dbOpenDynaset)    

    APPSESSION.Workspace.BeginTrans
    isInTrans_ = True

Exit_:
    Set db = Nothing

End Sub

' Append initial record to log table
Private Sub logLoadInit()
Dim info As New Dictionary
On Error GoTo Err_

    ' TODO raise error?
    If log_ Is Nothing Then GoTo Exit_   

    With info
        .add "loadTime", Now
        .add "loadBy", CurrentUser
    End With

    logid_ = DAOext.appendFromHash(log_, info, , APPSESSION.Workspace)

Exit_:
    Exit Sub

Err_:
    ' runtime error handling done here, code omitted
    Resume Exit_

End Sub

Private Sub logLoadOK(filename As String)
    logLoad logid_, True, filename, New Collection
End Sub

Private Sub logLoadFail(filename As String, _
                      errs As Collection)
    logLoad logid_, False, filename, errs
End Sub

' Update log table record added in logLoadInit
Private Sub logLoad(logID As Variant, _
                    isloaded As Boolean, _
                    filename As String, _
                    errs As Collection)

Dim info As New Dictionary
Dim er As Variant, strErrs As String
Dim ks As Variant, k As Variant
On Error GoTo Err_

    ' TODO raise error?
    If log_ Is Nothing Then GoTo Exit_   
    If IsNull(logID) Then GoTo Exit_

    For Each er In errs
        strErrs = strErrs & IIf(Len(strErrs) = 0, "", vbCrLf) & CStr(er)
    Next Er

    With info
        .add "loadTime", Now
        .add "loadBy", CurrentUser
        .add "loadRecs", nrecs
        .add "loadSuccess", isloaded
        .add "loadErrs", strErrs
        .add "origPath", filename
    End With

    log_.Requery
    log_.FindFirst "[logID]=" & Nz(logID)
    If log_.NoMatch Then
        'TODO raise error
    Else
        log_.Edit
        ks = info.Keys
        For Each k In ks
            log_.Fields(k).Value = info(k)
        Next k
        log_.Update
    End If

Exit_:
    Exit Sub

Err_:
    ' runtime error handling done here, code omitted
    Resume Exit_

End Sub


'-------------
' Class DAOExt
'-------------
' append to recordset from Dictionary, return autonumber id of new record
Public Function appendFromHash(rst As DAO.Recordset, _
                          rec As Dictionary, _
                          Optional map As Dictionary, _
                          Optional wrk As DAO.workspace) As Long
Dim flds() As Variant, vals() As Variant, ifld As Long, k As Variant
Dim f As DAO.Field, rst_id As DAO.Recordset
Dim isInTrans As Boolean, isPersistWrk As Boolean
On Error GoTo Err_

    ' set up map (code omitted here)

    For Each k In rec.Keys
        If Not map.Exists(CStr(k)) Then _
            Err.Raise 3265, "appendFromHash", "No field mapping found for [" & CStr(k) & "]"
        flds(ifld) = map(CStr(k))
        vals(ifld) = rec(CStr(k))
        ifld = ifld + 1
    Next k

    If wrk Is Nothing Then
        isPersistWrk = False
        Set wrk = DBEngine(0)
    End If

    wrk.BeginTrans
        isInTrans = True
        rst.AddNew
        With rst
            For ifld = 0 To UBound(flds)
                .Fields(flds(ifld)).Value = vals(ifld)
            Next ifld
        End With
        rst.Update

        Set rst_id = wrk(0).OpenRecordset("SELECT @@Identity", DAO.dbOpenForwardOnly, DAO.dbReadOnly)
        appendFromHash = rst_id.Fields(0).Value

    wrk.CommitTrans
    isInTrans = False

Exit_:
    On Error GoTo 0
    If isInTrans And Not wrk Is Nothing Then wrk.Rollback
    If Not isPersistWrk Then Set wrk = Nothing
    Exit Function

Err_:
    ' runtime error handling, code omitted here
    Resume Exit_

End Function


'-----------------
' Class DAOSession (the part that deals with the workspace and dbase connections)
'-----------------
Private wrk_ As DAO.workspace
Private connects_ As New Dictionary
Private dbs_ As New Dictionary

Public Property Get workspace() As DAO.workspace
    If wrk_ Is Nothing Then
        If DBEngine.Workspaces.Count > 0 Then
            Set wrk_ = DBEngine(0)
        End If
    End If
    Set workspace = wrk_
End Property

Public Property Get connectionTo(dbname As String) As DAO.database
    connectTo dbname
    Set connectionTo = connects_(dbname)
End Property

Public Sub connectTo(dbname As String)
Dim Cancel As Integer
Dim cnn As DAO.database
Dim opts As Dictionary
    Cancel = False

    ' if already connected, use cached reference
    If connects_.Exists(dbname) Then GoTo Exit_

    If wrk_ Is Nothing Then _
        Set wrk_ = DBEngine(0)

    ' note opts is a dictionary of connection options, code omitted here
    Set cnn = wrk_.OpenDatabase(dbs_(dbname), _
                                CInt(opts("DAO.OPTIONS")), _
                                CBool(opts("DAO.READONLY")), _
                                CStr(opts("DAO.CONNECT")))

    ' Cache reference to dbase connection
    connects_.Add dbname, cnn

Exit_:
    Set cnn = Nothing
    Exit Sub

End Sub
EN

回答 3

Stack Overflow用户

发布于 2010-05-26 22:39:46

事务是通过定义一个工作区(它不一定是一个新的工作空间)来使用的,然后在该工作区上开始事务,执行您需要对它做的事情,然后在一切正常的情况下执行事务。骨骼代码:

代码语言:javascript
复制
  On Error GoTo errHandler
    Dim wrk As DAO.Workspace

    Set wrk = DBEngine(0) ' use default workspace
    wrk.BeginTrans
    [do whatever]
    If [conditions are met] Then
       wrk.CommitTrans
    Else
       wrk.Rollback
    End If

  errHandler:
    Set wrk = Nothing

  exitRoutine:
    ' do whatever you're going to do with errors
    wrk.Rollback
    Resume errHandler

现在,在执行任何操作的块中,您可以将工作区、数据库和记录集传递给子例程,但是顶层控制结构应该保留在一个位置。

您的代码没有这样做--相反,您依赖于全局变量。全局变量是邪恶的。别用它们。相反,将私有变量作为参数传递给您想要操作的子程序。我还要说,永远不要传递工作区--只传递您用工作区创建的对象。

一旦您了解了这一点,也许它将帮助您解释您的代码应该完成什么任务(我对阅读它没有最模糊的想法),然后我们可以向您建议您做错了什么。

票数 3
EN

Stack Overflow用户

发布于 2010-05-27 05:41:38

好吧,经过很多令人沮丧的调试之后,我想我发现了Jet事务中的一个bug。毕竟,这与我的“非常复杂的”代码或“邪恶的全局变量”无关:)

当以下内容为真时,您将得到#3034的错误:

在启动transaction

  • The记录集之前,先打开记录集/取消引用的记录集,在开始事务后,但在提交或回滚之前。

<代码>F 211

我没有检查这是否已经知道,虽然我无法想象它不是。

当然,按照这样的顺序做事,自找麻烦是有点奇怪的,我不知道我为什么这么做。我将快照记录集打开/关闭移到事务中,一切正常。

以下代码显示了错误:

代码语言:javascript
复制
Public Sub run()
Dim db As DAO.Database, qdf As DAO.QueryDef, rst As DAO.Recordset
Dim wrk As DAO.Workspace, isInTrans As Boolean
On Error GoTo Err_

    Set wrk = DBEngine(0)
    Set db = wrk(0)
    Set rst = db.OpenRecordset("Table2", DAO.dbOpenSnapshot)

    wrk.BeginTrans
    isInTrans = True

    Set qdf = db.CreateQueryDef("", "INSERT INTO [Table1] (Field1, Field2) VALUES (""Blow"", ""Laugh"");")
    qdf.Execute dbFailOnError

Exit_:
    Set rst = Nothing
    Set qdf = Nothing
    Set db = Nothing
    If isInTrans Then wrk.CommitTrans
    isInTrans = False
    Exit Sub

Err_:
    MsgBox Err.Description
    If isInTrans Then wrk.Rollback
    isInTrans = False
    Resume Exit_

End Sub

这修复了错误:

代码语言:javascript
复制
Public Sub run()
Dim db As DAO.Database, qdf As DAO.QueryDef, rst As DAO.Recordset
Dim wrk As DAO.Workspace, isInTrans As Boolean
On Error GoTo Err_

    Set wrk = DBEngine(0)
    Set db = wrk(0)

    wrk.BeginTrans
    isInTrans = True

    ' NOTE THIS LINE MOVED WITHIN THE TRANSACTION
    Set rst = db.OpenRecordset("Table2", DAO.dbOpenSnapshot)

    Set qdf = db.CreateQueryDef("", "INSERT INTO [Table1] (Field1, Field2) VALUES (""Blow"", ""Laugh"");")
    qdf.Execute dbFailOnError

Exit_:
    Set rst = Nothing
    Set qdf = Nothing
    Set db = Nothing
    If isInTrans Then wrk.CommitTrans
    isInTrans = False
    Exit Sub

Err_:
    MsgBox Err.Description
    If isInTrans Then wrk.Rollback
    isInTrans = False
    Resume Exit_

End Sub
票数 2
EN

Stack Overflow用户

发布于 2011-04-01 02:51:57

就其价值而言,这似乎比仅仅访问事务更为广泛。我刚刚遇到了类似的情况,使用Access 2007 & DAO作为MySQL的前端。使用MySQL Autocommit=0,SQL事务将神秘地在事务进行到一半的时候提交自己。

经过2周的抓挠之后,我发现了这篇文章,并再次查看了我的代码。当然,MySQL插入是由从VBA类模块中调用的存储过程完成的。这个类模块有一个dao.recordset,它在module.initialize()上打开,在terminate()上关闭。此外,此记录集用于收集存储过程的结果。所以我有(用伪代码)

代码语言:javascript
复制
module.initialize - rs.open

class properties set by external functions

transaction.begins

Mysql procedure.calls using class properties as parameters - 

commit(or rollback)

rs.populate

class properties.set

properties used by external functions

module terminate - rs.close

而交易只是不起作用。我试了两周所有能想象到的东西。一旦我在交易中声明并关闭了rs,一切都工作得很完美!

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/2901408

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档