Access JumpStart 2.0 | Blog

A Rapid Development Framework for Microsoft Access

So I had some legacy code that was testing an mdb connection to make sure it could get an exclusive lock on the file. If so, the code would then proceed doing an update procedure, but switched methods to use ADO.

I am pretty certain that I wrote the code like 15 years ago when Microsoft had announced (and since renigged) that DAO was going away and ADO was going to replace it.

In any case, I had upgraded the Dbs to use ACCDB backends. This seems to have some “enhanced” functionality that allows you to open a database in exclusive mode even when others are in the database and so when I made the code live, multiple people in the database wreaked havoc on the routine and there had already been 2 instances where a table had been deleted and not recreated causing even more problems.

So, what was I going to do to get this working? I thought I would use a transaction to do the whole update process.

A transaction is something that allows you to try to do various SQL commands and if you get an error, you can programmatically “rollback” the commands. As if you never even started the process to begin with. And otherwise you can commit the transaction.to finalize it. This worked perfectly to prevent the process from trashing the database. here are the relevant sections of the function I rewrote:

Private Sub UpdateAll() 
    Dim WS As Workspace 
    Dim beDB As DAO.Database 
    Dim curDB As DAO.Database 
    Dim strBeDB As String 
    Dim I As Integer 
    Me.cmdDone.Enabled = False 
    DoCmd.Hourglass True 
    DoCmd.SetWarnings False 
    Set WS = DBEngine(0) 
    On Error GoTo ResumeOnError 
    strBeDB = AJSopt.AJS_BackEndDB_Path &  "\EakasGold_PlannedReqData.accdb"
    'Try to open the backend Exclusively
    Set beDB = WS.OpenDatabase(AJSopt.AJS_BackEndDB_Path &  "\EakasGold_PlannedReqData.accdb",True) 
    If beDB Is Nothing Then 
        'We could not get exclusive access
        MsgBox  "You need to have exclusive access to the database to run this Update.  Please make sure no one else is in Planned Requirements and try again."
        GoTo Exit_Sub 
    End If 
    Set curDB = WS(0) 
    On Error GoTo LogErrorAndAlertUser 
    WS.BeginTrans 
        'March weekly records up a week in the forecast table.
        Dim rsForecast As DAO.Recordset 
        Dim OldDate As Date 
        Dim NewDate As Date 
        OldDate = NiceDLookup( "ForecastStartDate", "Status") 
        NewDate = DateAdd( "ww",9,DateAdd( "d",1 - Weekday(Date),Date)) 
        curDB.Execute _ 
            "UPDATE Status SET ForecastStartDate = #" & NewDate &  "#"  'DateAdd( ""ww "" ,9 ,DateAdd( ""d "" ,1-Weekday(Date()) ,Date()))" 
        'Need to add code to change numbers in extForecast table if the ForecastStartDate has changed
        If DateDiff( "ww",OldDate,NewDate) > 0 Then 
            Dim numWeeks As Double 
            numWeeks = DateDiff( "ww",OldDate,NewDate) 
            For I = 9 To 39 - numWeeks 
                curDB.Execute ( "UPDATE ExtForecast SET Week" & I &  " = Week" & (I + numWeeks)) 
            Next I 
            For I = 39 - numWeeks + 1 To 39 
                curDB.Execute ( "UPDATE ExtForecast SET Week" & I &  " = 0") 
            Next I 
            'CurrentProject.Connection.Execute ("UPDATE ExtForecast SET Week0 = 0")
            WS.CommitTrans 
            WS.BeginTrans 
        End If 
    'Continue with update routine
        'Formerly query AddNewFamiliesToExtForecast
        curDB.Execute _ 
            "INSERT INTO ExtForecast(FamilyNumber)" & _ 
            "SELECT FamilyData.FamilyNumber " & _ 
            "FROM FamilyData " & _ 
            "WHERE (((FamilyData.FamilyNumber) Not In (Select FamilyNumber From ExtForecast)));"
        Me.lblStep1.Caption =  "Done!"
        Me.Repaint 
        Call DeleteTable( "MasterPartList",beDB) 
        curDB.Execute CreateTableMasterPartList(),dbSeeChanges 
        curDB.Execute  "UPDATE tmpMasterPartList t INNER JOIN Defaults_CartonSize cs ON " & _ 
            "('E' = cs.LocationKey AND t.Itemkey = cs.ItemKey) " & _ 
            "SET t.Carton_Size = Val(Nz(cs.CartonSize,0))"
        curDB.Execute  "UPDATE tmpMasterPartList t INNER JOIN Defaults_LeadTime lt ON " & _ 
            "('E' = lt.LocationKey AND t.Itemkey = lt.ItemKey) " & _ 
            "SET t.Lead_Time = Val(Nz(lt.LeadTime,0))"
        curDB.Execute  "UPDATE tmpMasterPartList t INNER JOIN Defaults_SafetyStock ss ON " & _ 
            "('E' = ss.LocationKey AND t.Itemkey = ss.ItemKey) " & _ 
            "SET t.MinimumStockQty = Val(Nz(ss.SafetyStock,0))"
        curDB.Execute  "SELECT * INTO MasterPartList IN '" & strBeDB &  "' FROM tmpMasterPartList"
        curDB.Execute  "DROP TABLE tmpMasterPartList"
            WS.CommitTrans 
            WS.BeginTrans 
        Call DeleteTable( "Family_ShipsPlusForecast",beDB) 
        curDB.Execute CreateTableFamily_ShipsPlusForecast(),dbSeeChanges 
        curDB.Execute  "SELECT * INTO Family_ShipsPlusForecast IN '" & strBeDB &  "' FROM tmpFamily_ShipsPlusForecast"
        curDB.Execute  "DROP TABLE tmpFamily_ShipsPlusForecast"
            WS.CommitTrans 
            WS.BeginTrans 
        Me.lblStep2.Caption =  "Done!"
        Me.lblStep3.Caption =  "Running Query"
        Me.Repaint 
        Call DeleteTable( "FinalShipsPlusForecastPlusOrdered",beDB) 
        curDB.Execute CreateTableFinalShipsPlusForecastPlusOrdered(),dbSeeChanges 
        curDB.Execute  "SELECT * INTO FinalShipsPlusForecastPlusOrdered IN '" & strBeDB &  "' FROM tmpFinalShipsPlusForecastPlusOrdered"
        curDB.Execute  "DROP TABLE tmpFinalShipsPlusForecastPlusOrdered"
            WS.CommitTrans 
            WS.BeginTrans 
        Me.lblStep3.Caption =  "Done!"
        Me.Repaint 
        
        Me.lblStep6.Caption =  "Done!"
        Me.Repaint 
        Dim rsInvMast As DAO.Recordset 
        Dim nextVal As Double 
        Dim arrInvMast As Variant 
        Set rsInvMast = curDB.OpenRecordset( "SELECT Trim(ItemKey),Nz(Qtyonhand,0) FROM MasterPartList ORDER BY ItemKey",_ 
            dbOpenSnapshot,dbSeeChanges,dbPessimistic) 
        'rsInvMast.MoveLast
        arrInvMast = rsInvMast.GetRows(rsInvMast.RecordCount) 
        rsInvMast.Close 
        Set rsInvMast = Nothing 
        Set rsForecast = curDB.OpenRecordset( "SELECT * FROM FinalShipsPlusForecastPlusOrdered ORDER BY ShippingDate ASC",_ 
            dbOpenDynaset,dbSeeChanges,dbPessimistic) 
        For I = 0 To UBound(arrInvMast,2) 
            DoEvents     ' Let any system events do their thing, otherwise the form display
                        ' code doesn't function properly
'            If I Mod 200 = 0 Then
'                rsForecast.Close
'                Set rsForecast = curDB.OpenRecordset("SELECT * FROM FinalShipsPlusForecastPlusOrdered ORDER BY ShippingDate ASC", _
'                    dbOpenDynaset, dbSeeChanges, dbPessimistic)
'            End If
            Me.lblStep4.Caption = (I + 1) &  " of " & (UBound(arrInvMast,2) + 1) 
            Me.Repaint 
            ' Filter the recordset using each itemkey from our database
            rsForecast.Filter =  "ItemKey = '" & arrInvMast(0,I) &  "'"
            If Not rsForecast.BOF And Not rsForecast.EOF Then 
            rsForecast.MoveFirst 
            rsForecast.Edit 
            rsForecast!OnHandQuantity = arrInvMast(1,I) 
            nextVal = rsForecast!OnHandQuantity - IIf(IsNull(rsForecast!RequiredQuantity),0,rsForecast!RequiredQuantity) + rsForecast!OrderedQuantity 
            rsForecast.Update 
            rsForecast.MoveNext 
            Do While Not rsForecast.EOF 
                rsForecast.Edit 
                rsForecast![OnHandQuantity] = nextVal 
                nextVal = rsForecast!OnHandQuantity - IIf(IsNull(rsForecast!RequiredQuantity),0,rsForecast!RequiredQuantity) + rsForecast!OrderedQuantity 
                rsForecast.Update 
                rsForecast.MoveNext 
            Loop 
            End If 
        Next 
        rsForecast.Close 
        Me.lblStep4.Caption =  "Done!"
        ' Update the Master part list with the requirements we just loaded
        curDB.Execute _ 
            "UPDATE MasterPartList AS mpl, FinalShipsPlusForecastPlusOrdered AS a " & _ 
            "SET Requirements = Nz(Requirements,0)+Nz(RequiredQuantity,0), " & _ 
            "QtyOnOrder = Nz(QtyOnOrder,0)+Nz(OrderedQuantity,0) " & _ 
            "WHERE a.ItemKey=mpl.ItemKey"
        Me.lblStep5.Caption =  "Done!"
        curDB.Execute _ 
            "UPDATE Status SET LastUpdated = Now()"
        WS.CommitTrans 
    GoTo Exit_Sub 
ResumeOnError: 
    AJS.GblErrHandler Action:= "INFO",MsgAction:= "LOGONLY"
    Resume Next 
LogErrorAndAlertUser: 
    'Stop
    'Resume
    WS.Rollback 
    AJS.GblErrHandler Action:= "RETURN",MsgAction:= "LOGONLY"
    MsgBox  "Update Failed, make sure no one is using the MRP application and try again."
    Resume Exit_Sub 
Exit_Sub: 
    If Not beDB Is Nothing Then beDB.Close 
    Set beDB = Nothing: Set WS = Nothing: Set curDB = Nothing 
    Forms!frmMain.lblLastUpdate.Requery 
    Forms!frmMain.Disclaimer.Visible = IIf(Forms!frmMain.LastUpdated >= Forms!frmMain.LastChange,False,True) 
    Forms!frmMain.Dirty = False 
    Me.cmdDone.Enabled = True 
    DoCmd.Hourglass False 
    DoCmd.SetWarnings True 
End Sub 

Actually, there it is in it’s entirety. I’d like to point out that I am at certain points committing the transaction and then restarting it with WS.CommitTrans and WS.BeginTrans. This attempts to commit all the changes up to that point on all databases in that workspace. I used Workspace(0) to set my WS variable which is the current workspace. This meant that any error logging I was doing was potentially getting rolled back if the transaction never finished because CurrentDb is actually Workspace(0)(0) and therefore was included in the transaction and could be rolled back if it was never committed.

I have two error handling subs, the first one to handle an expected potential error that will prevent the routine from working at all, and then the second one to rollback the transaction and alert the user to the failure and exit the sub after cleaning up after itself.

Ultimately, it was quite easy to make the references to the databases part of the Workspace variable and then simply use StartTrans, CommitTrans, and Rollback at the appropriate places to prevent unwanted changes to the database.

Sign up For a Daily Email Adventure in Microsoft Access

Every business day (typically M-F), I'll send you an email with information about my ongoing journey as an advanced Access application developer. It will be loaded with my tips and musings.

    We won't send you spam. Unsubscribe at any time.