When the error reappears error handling does not work - ms-access

Logics:
User. Presses the button;
Code . Creates a "Recordset" "rstStud" (students) and "rstGroupStud" (Student Groups);
Code . Cycle. Enumerates "rstStud";
Code . Add entry to "rstGroupStud";
Code . If the record exists, go to the next record in the loop;
Code . If the entry is new, add an entry in "rstGroupStud";
Essence: one click of a button - add one unique entry.
Problem.
When the cycle passes! StudentName = "Student Name 2" in the line ".Update" I get an error.
Error:
"Failed to make changes due to duplicate values ​​in
index, primary key or relationships. Change the data in one
or multiple fields containing duplicate values
delete the index or override it by allowing
duplicate values ​​and try again. "
In other words, the first time the error handler works normally, and when I re-fire, I get an error.
Question.
How to make the code work according to the described logic?
Private Sub btnAddRecord_Click()
Dim nameStud As String
Dim rstStud As DAO.Recordset '
Dim rstGroupStud As DAO.Recordset '
Set rstStud = CurrentDb.OpenRecordset("tbl_02_Students", dbOpenSnapshot) '
Set rstGroupStud = CurrentDb.OpenRecordset("tbl_03_GruopsStudents", dbOpenDynaset) '
' *** rstStud
With rstStud
Do Until .EOF = True
nameStud = !nameStud
On Error GoTo errend
' *** rstGroupStud
With rstGroupStud
.AddNew
!idGroup = Me.id_GroupFrm
!nameStud = nameStud
' nameStud
.Update
End With
rstGroupStud.Close
Me.frm_03_GruopsStudents_tbl.Requery
Exit Sub
errend:
.MoveNext
Loop
End With
On Error Resume Next
rstStud.Close
Set rstStud = Nothing
End Sub
Update_1
File - link

You need to de-tangle the execution paths; normal and error execution states are intertwined, that's why any error beyond the first one can't be handled.
Private Sub btnAddRecord_Click()
Dim nameStud As String
Dim rstStud As DAO.Recordset '
Dim rstGroupStud As DAO.Recordset '
Set rstStud = CurrentDb.OpenRecordset("tbl_02_Students", dbOpenSnapshot) '
Set rstGroupStud = CurrentDb.OpenRecordset("tbl_03_GruopsStudents", dbOpenDynaset) '
' *** rstStud
With rstStud
Do Until .EOF = True
On Error GoTo ErrHandler
nameStud = !nameStud
' *** rstGroupStud
With rstGroupStud
.AddNew
!idGroup = Me.id_GroupFrm
!nameStud = nameStud
' nameStud
.Update
End With
rstGroupStud.Close
Me.frm_03_GruopsStudents_tbl.Requery
Exit Do
TryNext:
On Error Resume Next
.MoveNext
If Err.Number <> 0 Then Exit Do
On Error GoTo 0
Loop
End With
On Error Resume Next
rstStud.Close
Set rstStud = Nothing
On Error GoTo 0
Exit Sub
ErrHandler:
Resume TryNext
End Sub
That way ErrHandler only ever runs in an error state; TryNext runs in the "happy path", and Exit Do breaks out of the loop (but not out of the procedure) so that the cleanup code can run whatever the outcome is.

Don't do it in VBA. Do it in a query instead.
For example, you could do this way:
Create a query named qryAssignStudentsToGroup:
PARAMETERS id_GroupFrm INT;
INSERT INTO tbl_03_GruopsStudents (idGroup, nameStud)
SELECT id_GroupFrm, nameStud
FROM tbl_02_Students AS s
WHERE NOT EXISTS (
SELECT NULL
FROM tbl_03_GruopsStudents AS g
WHERE s.nameStud = g.nameStud
AND g.idGroup = id_GroupFrm
);
Then your code behind becomes:
Private Sub btnAddRecord_Click()
With CurrentDb.QueryDefs("qryAssignStudentsToGroup")
.Parameters("id_GroupFrm") = Me.id_GroupFrm
.Execute
End With
Me.frm_03_GruopsStudents_tbl.Requery
End Sub
This gives you much less code for same thing, and you eliminate the complexity around error handling simply by filtering out the rows. More importantly, you perform a bulk update once, rather than for each row you touch. This leverages the power of database engine. It's convenient to do things in a loop with a DAO.Recordset but that's row-by-agonizing-row programming. You really want to get in mindset of set-based programming.

You must call Err.Clear to reset the error state
errend:
Err.Clear
.MoveNext
I would call Me.frm_03_GruopsStudents_tbl.Requery after the loop. There is not point in requerying the form all the time.
But jumping to another regular code part instead of going to an error handler is not the usual way of handling errors. To address the possibility to handle errors on MoveNext, change the code like this:
Private Sub btnAddRecord_Click()
Dim nameStud As String
Dim rstStud As DAO.Recordset
Dim rstGroupStud As DAO.Recordset
Set rstStud = CurrentDb.OpenRecordset("tbl_02_Students", dbOpenSnapshot)
Set rstGroupStud = CurrentDb.OpenRecordset("tbl_03_GruopsStudents", dbOpenDynaset)
' *** rstStud
With rstStud
Do Until .EOF = True
nameStud = !nameStud
On Error GoTo UpdateError
' *** rstGroupStud
With rstGroupStud
.AddNew
!idGroup = Me.id_GroupFrm
!nameStud = nameStud
' nameStud
.Update
End With
rstGroupStud.Close
Me.frm_03_GruopsStudents_tbl.Requery
Exit Sub
continue_loop:
On Error GoTo MoveNextError
.MoveNext
Loop
End With
CleanUp:
On Error Resume Next
rstStud.Close
Set rstStud = Nothing
Exit Sub
UpdateError:
Resume continue_loop
MoveNextError:
MsgBox Err.Description
Resume CleanUp
End Sub
This pattern is extensible. You can add as many error handlers as required.

Related

Ignore error 58 when renaming files

I've got a small Access program that looks up files names from a query ("qryImagesToRename"), goes through a loop and renames them. However, if an image already exists with the same name Access wants to rename it to, I receive
error 58 - File Already Exists
How do I ignore this error and continue with the loop? This my code:
Private Sub Command10_Click()
On Error GoTo Command10_Click_Error
Dim rs As DAO.Recordset
Dim db As DAO.Database
Dim strSQL As String
DoCmd.Hourglass True
Set db = CurrentDb
strSQL = "select * from qryImagesToRename"
Set rs = db.OpenRecordset(strSQL)
Do While Not rs.EOF
Name rs.Fields("From").Value As rs.Fields("To").Value
rs.MoveNext
Loop
DoCmd.Hourglass False
MsgBox "All matching files renamed"
On Error GoTo 0
Exit Sub
Command10_Click_Error:
MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure Command10_Click of VBA Document Form_frmRename - Please take a screenshot and email xxxxxx#xxxxxxx.com"
End Sub
If you are certain that you can ignore the error then you could use On Error Resume Next to ignore it and continue processing. Ensure that you add On Error Goto 0 as soon as you can, to reinstate the normal error processing.
On Error Resume Next
Do While Not rs.EOF
Name rs.Fields("From").Value As rs.Fields("To").Value
rs.MoveNext
Loop
On Error GoTo 0
This is most often a poor practice, but can be used if there is certainty about behaviour.
A better practice would be to check if the file already exists using Dir (or FileSystemObject) and skip it. Discussed here
Two particular solutions come to mind. The first, is in-line logic to check for the existing file, and skip that item, and the second is to put a case statement in the error handler. I have outlined the code below to have both options. I hope it helps.
Private Sub Command10_Click()
On Error GoTo Command10_Click_Error
Dim rs As DAO.Recordset
Dim db As DAO.Database
Dim strSQL As String
Dim fso as New FileSystemObject
DoCmd.Hourglass True
Set db = CurrentDb
strSQL = "select * from qryImagesToRename"
Set rs = db.OpenRecordset(strSQL)
Do While Not rs.EOF 'if you want to use the logic inline, use the check below
If fso.fileexists(rs.Fields("To").value) = false Then
Name rs.Fields("From").Value As rs.Fields("To").Value
End If
NextRecord: 'if you want to use the goto statement, use this
rs.MoveNext
Loop
DoCmd.Hourglass False
MsgBox "All matching files renamed"
On Error GoTo 0
Exit Sub
Command10_Click_Error:
Select case Err.number
Case 58
GoTo NextRecord
Case Else
MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure Command10_Click of VBA Document Form_frmRename - Please take a screenshot and email xxxxxx#xxxxxxx.com"
End select
End Sub

Running a database in run time version brings up an error

I've finished creating a database and it works perfectly on my computer. I'm using access 2013 and in my VBA code I have written error handler for each function/sub which I use in most databases.
However the users that it is designed for have Access run-time 2007 and every time i run it on their machine i get an un-trapped error "Execution of this application has stopped due to a run-time error".
Code for the command button.
Option Compare Database
Private Sub Command0_Click()
Dim ErrorStep As String
DoCmd.SetWarnings False
'-------------------------------------------------------------------------------------
' Procedure : Command0_Click
' Author : Chris Sparkes
' Date : 13/08/2013
'-------------------------------------------------------------------------------------
ErrorStep = "1 - Cleansing Records"
DoCmd.OpenQuery "qry1_3"
DoCmd.OpenQuery "qry4-7"
DoCmd.OpenQuery "qry9"
Call ExcelOutputReport
Exit_Command0_Click:
On Error GoTo 0
Exit Sub
Command0_Click_Error:
MsgBox "Error in procedure Command0_Click of VBA Document."
GoTo Exit_Command0_Click
On Error GoTo 0
End Sub
Public Function ExcelOutputReport()
Dim ErrorStep As String
DoCmd.SetWarnings False
'---------------------------------------------------------------------------------------
' Procedure : ExcelOutputReport
' Author : Chris Sparkes
' Date : 13/08/2013
'---------------------------------------------------------------------------------------
ErrorStep = "1 - Cleansing Records"
Dim dbLocal As DAO.Database
Dim tbloutput As DAO.Recordset
'DAO Declarations
Dim objExcel As New Excel.Application
Dim objWorkbook As Excel.Workbook
Dim objWorksheet As Excel.Worksheet
Dim IntCurrTask As Integer
Dim blurb As String
Set dbLocal = CurrentDb()
Set tbloutput = dbLocal.OpenRecordset("tbl_output")
Set objExcel = CreateObject("Excel.Application")
Set objWorkbook = objExcel.Workbooks.Open("G:\Enliven Sales Report\Envliven_Report_Template_1.xls")
Set objWorksheet = objWorkbook.Worksheets("Enliven")
objExcel.Visible = True
objWorkbook.Windows(1).Visible = True
tbloutput.MoveFirst
IntCurrTask = 2
Do While Not tbloutput.EOF
With objWorksheet
.Cells(IntCurrTask, 1).Value = tbloutput![CustomerOrderCode]
.Cells(IntCurrTask, 2).Value = tbloutput![CustomerCode]
.Cells(IntCurrTask, 3).Value = tbloutput![CustomerDescription]
.Cells(IntCurrTask, 4).Value = tbloutput![ItemCode]
.Cells(IntCurrTask, 5).Value = tbloutput![ItemDescription]
.Cells(IntCurrTask, 6).Value = tbloutput![DateOrderPlaced]
.Cells(IntCurrTask, 7).Value = tbloutput![CustomerDueDate]
.Cells(IntCurrTask, 8).Value = tbloutput![Quantity]
.Cells(IntCurrTask, 9).Value = tbloutput![ShippedQuantity]
End With
IntCurrTask = IntCurrTask + 1
tbloutput.MoveNext
Loop
tbloutput.Close
dbLocal.Close
DoCmd.SetWarnings True
Set tbloutput = Nothing
Set dbLocal = Nothing
Set objWorksheet = Nothing
Set objWorkbook = Nothing
Set objExcel = Nothing
Exit_ExcelOutputReport:
On Error GoTo 0
Exit Function
ExcelOutputReport_Error:
MsgBox "Error at in procedure ExcelOutputReport of VBA Document."
GoTo Exit_ExcelOutputReport
End Function
Has anyone got any ideas what may be causing this? The references should be fine as I'm using the same ones that I know have worked with different databases that I've made.
Thanks,
Chris
You have added error routines, but you did not activate them. At the beginning of your methods, add an On Error Goto statement:
Private Sub Command0_Click()
On Error Goto Command0_Click_Error
...
End Sub
Public Function ExcelOutputReport()
On Error Goto ExcelOutputReport_Error
...
End Sub
In your error routines, you should display (at least) the contents of Err.Description instead of a generic error message. Otherwise, you will have a really hard time tracking the source of errors. E.g.:
MsgBox "Error in procedure Command0_Click: " & Err.Description

VBScript to interrogate an Access database

I want to extract all the fields associated to my tables in my access database, to get an inventory of all the data objects. This has to populate a form I've created. I've copied an extract of code to determine whether an object in the database is a query or a table and I would like to alter this, if possible.
Any help will be appreciated
Option Compare Database
Option Explicit
Private Sub AddInventory(strContainer As String)
Dim con As DAO.Container
Dim db As DAO.Database
Dim doc As DAO.Document
Dim rst As DAO.Recordset
Dim intI As Integer
Dim strType As String
Dim varRetval As Variant
On Error GoTo HandleErr
' You could easily modify this, using the
' OpenDatabase() function, to work on any database,
' not just the current one.
varRetval = SysCmd(acSysCmdSetStatus, _
"Retrieving " & strContainer & " container information...")
Set db = CurrentDb
Set con = db.Containers(strContainer)
Set rst = db.OpenRecordset("zstblInventory")
For Each doc In con.Documents
If Not IsTemp(doc.Name) Then
' Handle the special queries case.
' Tables and queries are lumped together
' in the Tables container.
If strContainer = "Tables" Then
If IsTable(doc.Name) Then
strType = "Tables"
Else
strType = "Queries"
End If
Else
strType = strContainer
End If
rst.AddNew
rst("Container") = strType
rst("Owner") = doc.Owner
rst("Name") = doc.Name
rst("DateCreated") = doc.DateCreated
rst("LastUpdated") = doc.LastUpdated
rst.Update
End If
Next doc
ExitHere:
If Not rst Is Nothing Then
rst.Close
Set rst = Nothing
End If
Exit Sub
HandleErr:
MsgBox Err.Number & ": " & Err.Description, , _
"AddInventory"
Resume ExitHere
End Sub
Private Sub RebuildInventory()
On Error GoTo HandleErr
DoCmd.Hourglass True
Me.lstInventory.RowSource = ""
Call CreateInventory
Me.lstInventory.RowSource = "SELECT ID, Container, Name, " & _
"Format([DateCreated],'mm/dd/yy (h:nn am/pm)') AS [Creation Date], " & _
"Format([lastUpdated],'mm/dd/yy (h:nn am/pm)') AS [Last Updated], " & _
"Owner FROM zstblInventory ORDER BY Container, Name;"
ExitHere:
DoCmd.Hourglass False
Exit Sub
HandleErr:
Resume ExitHere
End Sub
Private Sub CreateInventory()
If (CreateTable()) Then
' These routines use the status line,
' so clear it once everyone's done.
Call AddInventory("Tables")
Call AddInventory("Forms")
Call AddInventory("Reports")
Call AddInventory("Scripts")
Call AddInventory("Modules")
Call AddInventory("Relationships")
' Clear out the status bar.
Call SysCmd(acSysCmdClearStatus)
Else
MsgBox "Unable to create zstblInventory."
End If
End Sub
Private Function CreateTable() As Boolean
' Return True on success, False otherwise
Dim qdf As DAO.QueryDef
Dim db As DAO.Database
Dim strSQL As String
On Error GoTo HandleErr
Set db = CurrentDb()
db.Execute "DROP TABLE zstblInventory"
' Create zstblInventory
strSQL = "CREATE TABLE zstblInventory (Name Text (255), " & _
"Container Text (50), DateCreated DateTime, " & _
"LastUpdated DateTime, Owner Text (50), " & _
"ID AutoIncrement Constraint PrimaryKey PRIMARY KEY)"
db.Execute strSQL
' If you got here, you succeeded!
db.TableDefs.Refresh
CreateTable = True
ExitHere:
Exit Function
HandleErr:
Select Case Err
Case 3376, 3011 ' Table or Object not found
Resume Next
Case Else
CreateTable = False
End Select
Resume ExitHere
End Function
Private Function IsTable(ByVal strName As String)
Dim tdf As DAO.TableDef
Dim db As DAO.Database
On Error Resume Next
' Normally, in a function like this,
' you would need to refresh the tabledefs
' collection for each call to the function.
' Since this slows down the function
' by a very large measure, this time,
' just Refresh the collection the first
' time, before you call this function.
Set db = CurrentDb()
' See CreateTable().
'db.Tabledefs.Refresh
Set tdf = db.TableDefs(strName)
IsTable = (Err.Number = 0)
Err.Clear
End Function
Private Function IsTemp(ByVal strName As String)
IsTemp = Left(strName, 7) = "~TMPCLP"
End Function
Private Sub cmdCreateInventory_Click()
Call RebuildInventory
End Sub
Private Sub Detail0_Click()
End Sub
Private Sub Form_Open(Cancel As Integer)
Call RebuildInventory
End Sub
Check out the source code in this answer. You should be able to modify it to do what you need. Unless, as Remou pointed out in his comment, you are working with a pre-2000 version of Access.

MS Access (Jet) transactions, workspaces

I am having trouble with committing a transaction (using Access 2003 DAO). It's acting as if I never had called BeginTrans -- I get error 3034 on CommitTrans, "You tried to commit or rollback a transaction without first beginning a transaction"; and the changes are written to the database (presumably because they were never wrapped in a transaction). However, BeginTrans is run, if you step through it.
I am running it within the Access environment using the DBEngine(0) workspace.
The tables I'm adding records to are all opened via a Jet database connection (to the same database) and using DAO.Recordset.AddNew / Update.
The connection is opened before starting BeforeTrans.
I'm not doing anything weird in the middle of the transaction like closing/opening connections or multiple workspaces etc.
There are two nested transaction levels. Basically it's wrapping multiple inserts in an outer transaction, so if any fail they all fail. The inner transactions run without errors, it's the outer transaction that doesn't work.
Here are a few things I've looked into and ruled out:
The transaction is spread across several methods and BeginTrans and CommitTrans (and Rollback) are all in different places. But when I tried a simple test of running a transaction this way, it doesn't seem like this should matter.
I thought maybe the database connection gets closed when it goes out of local scope, even though I have another 'global' reference to it (I'm never sure what DAO does with dbase connections to be honest). But this seems not to be the case -- right before the commit, the connection and its recordsets are alive (I can check their properties, EOF = False, etc.)
My CommitTrans and Rollback are done within event callbacks. (Very basically: a parser program is throwing an 'onLoad' event at the end of parsing, which I am handling by either committing or rolling back the inserts I made during processing, depending on if any errors occurred.) However, again, trying a simple test, it doesn't seem like this should matter.
Any ideas why this isn't working for me?
Thanks.
EDIT 25 May
Here is the (simplified) code. The key points having to do with the transaction are:
The workspace is DBEngine(0), referenced within 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.
Rollback is called in the process__onInvalid callback.
Recordset updates are done 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
Transactions are used by defining a workspace (it doesn't have to be a new one) and then beginning the transaction on that workspace, doing what you need to do with it, and then commiting the transaction if all is well. Skeletal code:
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
Now, within the block where you [do whatever], you can pass off the workspace and databases and recordsets to subroutines, but the top-level control structure should remain in one place.
Your code does not do that -- instead, you depend on global variables. GLOBAL VARIABLES ARE EVIL. Don't use them. Instead, pass private variables as parameters to the subroutines you want to operate on them. I would also say, never pass the workspace -- only pass the objects you've created with the workspace.
Once you've absorbed that, maybe it will help you explain what your code is supposed to accomplish (which I haven't the foggiest idea of from reading through it), and then we can advise you as to what you're doing wrong.
OK, after much frustrating debugging, I think I uncovered a bug in Jet transactions. After all that, it has nothing to do with my "enormously convoluted" code or "evil global variables" :)
It appears that when the following is true, you get the error #3034:
You open a snapshot-type recordset
The recordset is opened before you start the transaction
The recordset is closed/dereferenced after you begin the transaction, but before the commit or rollback.
I haven't checked if this is already known, although I can't imagine it isn't.
Of course, it's kind of weird to do things in this order anyway and asking for trouble, I don't know why I did it. I moved opening/closing the snapshot recordset to within the transaction and everything works fine.
The following code exhibits the error:
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
And this fixes the error:
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
For what it is worth this seems to be a bit more widespread than just Access transactions. I have just encountered a similar situation using Access 2007 & DAO as a front end to MySQL. With MySQL Autocommit=0, The SQL transactions would nonetheless mysteriously commit themselves half way through a transaction.
After 2 weeks of head scratching I came across this post and looked at my code again. Sure enough, the MySQL inserts were being done by a Stored procedure that was called from within a VBA class module. This class module had a dao.recordset that was opened on module.initialize() and closed on terminate(). Furthermore, this recordset was used to collect the results of the stored procedure. So I had (in pseudo code...)
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
and the transactions were just not working. I tried everything imaginable for 2 weeks.
Once I declared and closed the rs within the transaction everything worked perfectly!

MS Access: How to bypass/suppress an error?

I'm executing a query like this
select field from table;
In that query, there is a loop running on many tables. So, if the field is not present in a table I get a
Runtime Error 3061
How can I by pass this error such as that on this error flow should go to another point?
This is the code I have recently after going through this forum.
Option Explicit
Private Sub UpdateNulls()
Dim rs2 As DAO.Recordset
Dim tdf As DAO.TableDef
Dim db As Database
Dim varii As Variant, strField As String
Dim strsql As String, strsql2 As String, strsql3 As String
Dim astrFields As Variant
Dim intIx As Integer
Dim field As Variant
Dim astrvalidcodes As Variant
Dim found As Boolean
Dim v As Variant
Open "C:\Documents and Settings\Desktop\testfile.txt" For Input As #1
varii = ""
Do While Not EOF(1)
Line Input #1, strField
varii = varii & "," & strField
Loop
Close #1
astrFields = Split(varii, ",") 'Element 0 empty
For intIx = 1 To UBound(astrFields)
'Function ListFieldDescriptions()
Dim cn As New ADODB.Connection, cn2 As New ADODB.Connection
Dim rs As ADODB.Recordset, rs3 As ADODB.Recordset
Dim connString As String
Dim SelectFieldName
Set cn = CurrentProject.Connection
SelectFieldName = astrFields(intIx)
Set rs = cn.OpenSchema(adSchemaColumns, Array(Empty, Empty, Empty, SelectFieldName))
'Show the tables that have been selected '
While Not rs.EOF
'Exclude MS system tables '
If Left(rs!Table_Name, 4) <> "MSys" Then
strsql = "Select t.* From [" & rs!Table_Name & "] t Inner Join 01UMWELT On t.fall = [01UMWELT].fall Where [01UMWELT].Status = 4"
End If
Set rs3 = CurrentDb.OpenRecordset(strsql)
'End Function
strsql2 = "SELECT label.validcode FROM variablen s INNER JOIN label ON s.id=label.variablenid WHERE varname='" & astrFields(intIx) & "'"
Set db = OpenDatabase("C:\Documents and Settings\Desktop\Codebook.mdb")
Set rs2 = db.OpenRecordset(strsql2)
With rs2
.MoveLast
.MoveFirst
astrvalidcodes = rs2.GetRows(.RecordCount)
.Close '
End With
With rs3
.MoveFirst
While Not rs3.EOF
found = False
For Each v In astrvalidcodes
If v = .Fields(0) Then
found = True
Debug.Print .Fields(0)
Debug.Print .Fields(1)
Exit For
End If
Next
If Not found Then
msgbox "xxxxxxxxxxxxxxxx"
End If
End If
.MoveNext
Wend
End With
On Error GoTo 0 'End of special handling
Wend
Next intIx
End Sub
I'm getting a
Type Mismatch Runtime Error
in Set rs3 = CurrentDb.OpenRecordset(strsql)
I guess I'm mixing up ado and dao but I'm not certainly sure where it is.
Use the On Error statement that VBA supplies:
Sub TableTest
On Error Goto TableTest_Error
' ...code that can fail... '
Exit Sub
:TableTest_Error
If Err.Number = 3061 Then
Err.Clear()
DoSomething()
Else
MsgBox Err.Description ' or whatever you find appropriate '
End If
End Sub
Alternatively, you can switch off automatic error handling (e.g. breaking execution and displaying an error message) on a line-by-line basis:
Sub TableTest
' ... fail-safe code ... '
On Error Resume Next
' ...code that can fail... '
If Err.Number = 3061 Then
Err.Clear()
DoSomething()
Else
MsgBox Err.Description
End If
On Error Goto 0
' ...mode fail-safe code... '
End Sub
There are these statements available:
On Error Resume Next switches off VBA-integrated error handling (message box etc.) completely, execution simply resumes on the next line. Be sure to check for an error very early after you've used that, as a dangling error can disrupt the normal execution flow. Clear the error as soon as you caught it to prevent that.
On Error Goto <Jump Label> resumes execution at a given label, primarily used for per-function error handlers that catch all sorts of errors.
On Error Goto <Line Number> resumes at a given line number. Stay away from that, it's not useful, even dangerous.
On Error Goto 0 it's close cousin. Reinstates the VBA integrated error management (message box etc.)
EDIT
From the edited qestion, this is my proposal to solve your problem.
For Each FieldName In FieldNames ' assuming you have some looping construct here '
strsql3 = "SELECT " & FieldName & " FROM table"
On Error Resume Next
Set rs3 = CurrentDb.OpenRecordset(strsql3)
If Err.Number = 3061 Then
' Do nothing. We dont care about this error '
Err.Clear
Else
MsgBox "Uncaught error number " & Err.Number & " (" & Err.Description & ")"
Err.Clear
End If
On Error GoTo 0
Next FieldName
Be sure to clear the error in any case before you go on with a loop in the same Sub or Function. As I said, a dangling error causes code flow to become unexpected!
Rather than trapping the error, why not use the TableDefs to check for the field or use a mixture of ADO and DAO? ADO Schemas can provide a list of tables that contain the required field:
Function ListTablesContainingField()
Dim cn As New ADODB.Connection, cn2 As New ADODB.Connection
Dim rs As ADODB.Recordset, rs2 As ADODB.Recordset
Dim connString As String
Dim SelectFieldName
Set cn = CurrentProject.Connection
SelectFieldName = "Fall" 'For tksy '
'Get names of all tables that have a column called 'ID' '
Set rs = cn.OpenSchema(adSchemaColumns, _
Array(Empty, Empty, Empty, SelectFieldName))
'Show the tables that have been selected '
While Not rs.EOF
'Exclude MS system tables '
If Left(rs!Table_Name, 4) <> "MSys" Then
' Edit for tksy, who is using more than one forum '
If tdf.Name = "01UMWELT" Then
strSQL = "Select * From 01UMWELT Where Status = 5"
Else
strSQL = "Select a.* From [" & rs!Table_Name _
& "] a Inner Join 01UMWELT On a.fall = 01UMWELT.fall " _
& "Where 01UMWELT.Status = 5"
End If
Set rs2 = CurrentDb.OpenRecordset(strSQL)
Do While Not rs2.EOF
For i = 0 To rs2.Fields.Count - 1
If IsNull(rs2.Fields(i)) Then
rs2.Edit
rs2.Fields(i) = 111111
rs2.Update
End If
Next
rs2.MoveNext
Loop
End If
rs.MoveNext
Wend
rs.Close
Set cn = Nothing
End Function
Try this:
On Error Resume Next ' If an error occurs, move to next statement.
...statement that tries the select...
If (Err <> 0) Then
...act on error, or simply ignore if necessary...
End If
On Error Goto 0 ' Reset error handling to previous state.