I am having trouble committing a transaction (using Access 2003 DAO). It acts as if I never called BeginTrans - I get error 3034 on CommitTrans: "You tried to complete a transaction or cancel a transaction without starting a transaction"; and the changes are written to the database (presumably because they were never wrapped in a transaction). However, BeginTrans starts if you go through it.
- I run it in an Access environment using the DBEngine (0) workspace.
- The tables to which I add records are opened through a connection to the Jet database (in the same database) and using DAO.Recordset.AddNew / Update.
- The connection opens before starting BeforeTrans.
- I am not doing anything strange in the middle of a transaction, such as closing / opening connections or multiple workspaces, etc.
- There are two levels of nested transactions. Basically it is packing multiple inserts into an external transaction, so if there is any failure, they all fail. Internal transactions are performed without errors; this is an external transaction that does not work.
Here are a few things I learned and ruled out:
A transaction is distributed using several methods, and BeginTrans and CommitTrans (and rollback) are in different places. But when I tried a simple test to complete a transaction this way, it doesn't seem to matter.
I thought the database connection closes when it leaves the local area, although I have another global reference to it (I never know what the DAO does with dbase connections, to be honest). But that doesn't seem to be the case - right before committing, the connection and its record sets are alive (I can check their properties, EOF = False, etc.)
My CommitTrans and rollback are done in callbacks. (Basically: the syntax program throws an onLoad event at the end of the parsing, which I process, either committing or rolling back the inserts that I made during processing, depending on whether any errors occurred.) However, again, trying a simple test, it seems like it doesn't matter.
Any ideas why this is not working for me?
Thanks.
EDIT May 25
Here is the (simplified) code. The key points associated with the transaction are:
- The workspace is DBEngine (0), which is referenced by the public (global) variable
APPSESSION . - The database connection is opened in LoadProcess.cache below, see the line
Set db = APPSESSION.connectionTo(dbname_) . - BeginTrans is called in LoadProcess.cache.
- CommitTrans is called in the process__onLoad callback.
- A rollback in the call to process__onInvalid is called.
- Recordset updates are performed in process__onLoadRow, logLoadInit, and logLoad
Eric
'------------------- '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