What is causing the delay between recordset.update and the form/report getting the information? - vba

Short version
I'm entering information in a database and fetching it shortly after, but for some reason, when I enter the information, it isn't immediately entered, so that when I try to fetch it, I get old results. Why does this happen? I thought the operations were synchronous.
Long version
I have a split Access database. At the moment the backend is on my own hard drive to speed up testing, eventually this backend will land on a server. Back when it was a combined frontend/backend database and before I had done a major code refactor (tbh, it was quite the clusterfornication before that), and now this is happening in a number of different scenarios, but pretty much every time I enter information and try to fetch it right after that. Why this happens is a mystery to me, since everything I was reading told me there is no multi-threading in VBA and that everything is synchronous if not specified otherwise, and I haven't enabled any asynchronous options.
Two Examples:
I add a record to the database then refresh the form that contains those new records. I'm not going to post the full code (unless it is deemed necessary), since I've modularized the code a lot. But essentially it boils down to this: the user clicks a button which executes this:
Private Sub Anhang_hinzufügen_Click()
If IsNull(Me.Parent.ID) Then
MsgBox "Bitte erst Felder ausfüllen, und anschließend Anhänge hinzufügen", vbInformation
Else
AnhängeAuswählen Me.Parent.Name, Me.Parent.ID
Me.Form.Requery
End If
End Sub
As part of the AnhängeAuswählen method, the method AddRecord is called:
Function AddRecord(TableName As String, fields() As String, values) As Long
Dim Table As DAO.Recordset
Set Table = LUKSVDB.OpenRecordset(TableName)
Table.AddNew
For i = LBound(fields) To UBound(fields)
If TypeName(Table.fields(fields(i)).Value) = "Recordset2" Then
Dim rs2 As DAO.Recordset2
Set rs2 = Table.fields(fields(i)).Value
If IsArray(values(i)) Then
For j = LBound(values(i)) To UBound(values(i))
rs2.AddNew
rs2!Value = values(i)(j)
rs2.Update
Next j
Else
rs2.AddNew
rs2!Value = values(i)
rs2.Update
End If
Else
Table.fields(fields(i)) = values(i)
End If
Next i
AddRecord = Table!ID
Table.Update
Table.Close
End Function
The record is created, that's not the problem. But when it executes Me.Form.Requery, the new record doesn't appear in the form. Only when I execute Me.Form.Requery a fraction of a second later does the record appear.
I add a record to the database using a form, update some information in the recordset with VBA, then requery the subreport with the records. The record appears immediately, but the details I added programmatically only appear when I execute Me.Parent.Requery a couple of seconds later.
The first form is a data entry form, so that as soon as the data is saved, it's blank in order to create a new record. The previous should then appear in the form. The button to create the new record looks like this:
Private Sub Anmerkung_Hinzufügen_Click()
currentID = Me.ID
mSaved = True
If Me.Dirty Then Me.Dirty = False
UpdateRecord "Anmerkungen", currentID, StringArray("Person", "Datum"), Array(User, Now)
Me.Parent.Requery
End Sub
The UpdateRecord is similar to the AddRecord method:
Function UpdateRecord(TableName As String, ByVal ID As Integer, fields() As String, values)
Dim Table As DAO.Recordset
Set Table = SeekPK(TableName, ID, True)
Table.Edit
For i = LBound(fields) To UBound(fields)
If TypeName(Table.fields(fields(i)).Value) = "Recordset2" Then
Dim subtable As DAO.Recordset2
Set subtable = Table.fields(fields(i)).Value
If IsArray(values(i)) Then
On Error Resume Next
Dim t
t = LBound(values(i))
If Developer Then On Error GoTo -1 Else On Error GoTo Fehler
If Err.Number = 0 Then
For j = LBound(values(i)) To UBound(values(i))
subtable.AddNew
subtable!Value = values(i)(j)
subtable.Update
Next j
End If
Else
subtable.AddNew
subtable!Value = values(i)
subtable.Update
End If
Else
Table.fields(fields(i)) = values(i)
End If
Next i
Table.Update
Table.Close
End Function
Does anyone know why this happens, and how I can prevent it? I could do a bit of a workaround with timers on the forms, so that it refreshes the form a couple of seconds later, but that seems like a kludgy workaround to me, especially considering I don't know how long it specifically takes, and the times could change drastically once the backend is on the server.
Additional information, in case it's necessary:
In the code I've posted I've removed some additional code for error handling and performance logging, but it doesn't have any impact on what's happening otherwise.
When the database is opened, a global variable LUKSVDB As DAO.Database is initialized:
Function ConnectDatabase(Backend As Integer)
Select Case Backend
Case 0: DatenOrt = 'redacted, folder in which the production/beta database is located on the server
Case 1: DatenOrt = 'redacted, folder in which I have a personal testing database on the server
Case 2: DatenOrt = 'redacted, folder in which I have the testing database on my own computer
End Select
Set LUKSVDB = OpenDatabase(DatenOrt & "\LUKS-Verwaltung_be.accdb", False, False, ";pwd=PASSWORD")
End Function
For testing purposes, ConnectDatabase is launched with a value of 2. However, if it's a problem on my own SSD, where latency is just about 0, then I can only assume it will be a problem on the server as well, where the latency is definitely not 0.

Related

"NotOnList" event not recognizing that new data has, in fact, been added

This used to work but now it doesn't. Did Microsoft change something?
I'm attempting to understand the behavior of a combo box in MS Access. I created a table named "Colors" with 2 fields: ID (Autonumber, primary key) and Color (short text). I made a form with a combobox with "LimitToList" set to Yes. The Row Source is the table "Colors". For the NotInList event I have the following code:
Private Sub Colors_NotInList(NewData As String, Response As Integer)
Dim rst As DAO.Recordset
If MsgBox(NewData & " is not in list. Add it?", vbOKCancel) = vbOK Then
Response = acDataErrAdded
Set rst = CurrentDb.OpenRecordset("Colors")
With rst
.AddNew
!Color = NewData
.Update
End With
Else
Response = acDataErrContinue
ctl.Undo
End If
End Sub
If I enter a color not in the table I get the prompt to add it to the list. On clicking OK I get the Access error that I must select an item on the list. The new color I just entered does appear on the list and if I select it everything works fine. But I didn't used to have this second step of selecting it after it's been entered.
I've spent way to much time on what should be a simple task. Can anyone help? Thanks!

VBA: Interact with Access from within Outlook

I am trying to create some custom buttons in Outlook that interact with a table contained within an Access database. So far I have my buttons working in Outlook, running code that instantiates a custom data access class which in turn handles opening and closing the connection to the database. So far as I can tell, this much works.
However from this class I cannot even perform a simple select query. Can anyone help me understand why the code below might not work? I always end out with a recordset that has no rows but if I run the same sql using the Access query designer it works fine.
Public Function GetJobID(ByVal xEmailID As String) As Integer
'Returns the JobID associated with a given EmailID from the email link table.
'Returns a fail constant if no link exists.
Dim rs As ADODB.Recordset
Dim sql As String
'Exit if not connected.
'Cast to boolean because VBA doesn't recognise connection state integer as boolean.
If Not CBool(mConn.State) Then
GetJobID = RESULT_FAIL_INTEGER
Exit Function
End If
sql = "SELECT [JobID] FROM [EMAIL_LINK_TABLE] WHERE [EmailID]='xEmailID'"
sql = Replace(sql, "EMAIL_LINK_TABLE", EMAIL_LINK_TABLE)
sql = Replace(sql, "xEmailID", xEmailID)
On Error Resume Next
Set rs = mConn.Execute(sql)
If rs.RecordCount > 0 Then
GetJobID = rs(1).Value
Else
GetJobID = RESULT_FAIL_INTEGER
End If
End Function
I see you've tracked down the issue to .RecordCount returning -1.
This is standard behavior for dynamic cursors, from the docs:
The cursor type of the Recordset object affects whether the number of records can be determined. The RecordCount property will return -1 for a forward-only cursor; the actual count for a static or keyset cursor; and either -1 or the actual count for a dynamic cursor, depending on the data source.
Of course, you can modify your code to use a static cursor, but that will impact performance. Instead, to test if there are records in your recordset, use .EOF (a method returning a boolean to indicate if the recordset is currently at the end of the file). That will save your code from having to load all records, when only loading the first one is required:
Public Function GetJobID(ByVal xEmailID As String) As Integer
'Returns the JobID associated with a given EmailID from the email link table.
'Returns a fail constant if no link exists.
Dim rs As ADODB.Recordset
Dim sql As String
'Exit if not connected.
'Cast to boolean because VBA doesn't recognise connection state integer as boolean.
If Not CBool(mConn.State) Then
GetJobID = RESULT_FAIL_INTEGER
Exit Function
End If
sql = "SELECT [JobID] FROM [EMAIL_LINK_TABLE] WHERE [EmailID]='xEmailID'"
sql = Replace(sql, "EMAIL_LINK_TABLE", EMAIL_LINK_TABLE)
sql = Replace(sql, "xEmailID", xEmailID)
On Error Resume Next
Set rs = mConn.Execute(sql)
If Not rs.EOF Then
GetJobID = rs(0).Value
Else
GetJobID = RESULT_FAIL_INTEGER
End If
End Function

Loading Userform with Recordet Information Of Selected Item (How to make faster)

Im having a very minor issue, but is an issue nonetheless and its driving me nuts!
I have a Userform (VBA, using Excel as the Front End and Access as the Back End) which hast textboxes and a listbox.
What it does is fill the textboxes with information from an Access Database based on the selection the user makes on the listbox.
So if the user selects the entry with the "001" code, it goes to the Access DB, fecthes that record and populates the UserForm.
Below is the code:
Private Sub LtaInversiones_Click()
Dim rcon As Recordset
Dim sql As String
sql = "SELECT * FROM INVERSIONES WHERE CODIGO = "_
& LtaInversiones.List(LtaInversiones.ListIndex, 0)
Set rcon = BD.OpenRecordset(sql)
With rcon
CmbCodigo.Text = !Codigo
CmbTipo.Text = !TIPO
TxtTitulo.Text = !TITULO
TxtMonto.Text = !Monto
DTFCompra.Value = !FECHACOMPRA
DTFVencimiento.Value = !FECHAVENCIMIENTO
CmbPeriodicidad.Text = !periodicidad
TxtTCupon.Text = !TASACUPON
TxtPrecio.Text = !Precio
TxtRendimiento.Text = !rendimiento
TxtGPRedencion.Text = !GANANCIAPERDIDAREDENCION
TxtIAcum.Text = !INTERESESACUMULADOS
CmbEmisor.Text = !eMISOR
CmbOperador.Text = !OPERADOR
TxtNotas.Text = !NOTAS
If !FECHAREDENCION <> "" Then
DTFRedencion.Value = !FECHAREDENCION
ChkCInversion.Value = True
Else
ChkCInversion.Value = False
End If
.Close
End With
Set rcon = Nothing
CmdEliminar.Visible = True
CmdGuardar.Caption = "Modificar"
CmbCodigo.Enabled = True
The thing is, its working, but it slows down a bit. Ive tested the possible reasons, and it is definitely the fact that the query has to go look at what the list index is before going to the DB.
So I would like to ask you more experienced programmers (Im a lawyer by trade :/ ) If there is a better way of doing this.
The only thing that has ocurred to me is maybe loading all the records on initialization and then somehow accessing that data, as that would prevent the trip to the DB but am unsure whether that would improve performance or how to do it for that matter.
Thanks
What's stopping you from changing this:
LtaInversiones.List(LtaInversiones.ListIndex, 0)
to this?
LtaInversiones.Value
This will avoid a double lookup (List & ListIndex)
I personally avoid the "!" notation, preferring the fuller .Fields("ABC").Value
I'm not sure of any performance penalty or advantage there.

vba functions malfunction after repetitive calling

I have a database where data goes through multiple steps, and user can report and 'solve' those errors in the database.
Once an error is added in my tbl_errors, they go on and solve it. Once they have solved the error irl, the 'solve' the error in the database as well, to keep track of time and such.
This all works like a charm, when adding errors I have never encountered any problems. And at first sight, 'solving' the problems goes flawless either. The problem however, is that once I start 'solving' a lot of errors in a row, my code suddenly stops working.
It does not freeze or throw back any errors, and when I step through the code using my breakpoints and f8, all the variables seem to be correct also. Everything goes on just as always, except it just does not do anything anymore. This is only applicable to that specific error. When I add new errors, and try to 'solve' them. It works just as usual.
Important notes:
This ONLY happens when I start fast clicking on my solve button, thus calling the functions real fast behind eachother.
It only freezes for a specific errors. (Can be multiple) All other errors can be solved as usual, indicating that the code is still functioning.
I have stepped through the whole code, while checking all the keys and variables and every variable is correct.
Even though my code goes through the recordset, it does not update anything?
Below is a piece of my relationships screen to give a better understanding of the table structure, as well as the specific parts of code.
Calling the code in the OnClick event:
Private Sub solve_Click()
SolveError getorderid(gvStepDelivery), get_errorID(gvCategory, get_stepsID(gvStepNr))
Me.qry_errors_subform.Requery
Me.Refresh
End Sub
The self-written function SolveError:
Public Function SolveError(Current_order_ID As Long, Category_ID As Long)
Dim rs As Recordset
Set rs = CurrentDb.OpenRecordset("tbl_errors", dbOpenDynaset)
With rs
.FindFirst "[Error_ID] = " & DLookup("Error_ID", "tbl_errors", "[Current_orders_ID] = " & Current_order_ID & " AND [Category_ID] = " & Category_ID)
.Edit
![Solved_By] = get_user
![Solved_Date] = Date
![Solved_Time] = Time
.update
End With
rs.Close
Set rs = Nothing
End Function
There are other parts of code involved (See the SolveError's parameters), but I don't think they will add some usefull info, since they are just returning the correct values. (They are correct!!)
Ok, it seems I have already found my answer. Since I was probably calling a new iteration before the previous one had completely finished, it simply stopped working. Adding the DoEvents function at the end my SolveErrors function solved it. I have yet to experience the problem again.
Public Function SolveError(Current_order_ID As Long, Category_ID As Long)
Dim rs As Recordset
Set rs = CurrentDb.OpenRecordset("tbl_errors", dbOpenDynaset)
With rs
.FindFirst "[Error_ID] = " & DLookup("Error_ID", "tbl_errors", "[Current_orders_ID] = " & Current_order_ID & " AND [Category_ID] = " & Category_ID)
.Edit
![Solved_By] = get_user
![Solved_Date] = Date
![Solved_Time] = Time
.update
End With
rs.Close
Set rs = Nothing
DoEvents 'This one did the trick!!
End Function
Info on the DoEvents method can be found here: http://msdn.microsoft.com/en-us/library/system.windows.forms.application.doevents(v=vs.110).aspx?cs-save-lang=1&cs-lang=vb#code-snippet-1
and here: http://office.microsoft.com/en-001/access-help/doevents-function-HA001228827.aspx

The 'If' statement part is not being carried out

I'm currently working on a project for school and it includes a login and register system.
This is a function that I have made. The "taken" variable will be passed back to the main program.
Dim taken As Boolean
Dim temp As String
For counter = 1 To totalrecords
FileGet(1, player_info)
temp = player_info.username
If TextBox2.Text = temp Then
msgbox("this is a messagebox")
taken = True
End If
Next
This is the part of the code that checks if any usernames are in use. If so, taken = true and then a message is displayed.
Now, for some reason the if statement part is not being carried out. The message box does not show at all. I have tested this by using more than one same username and the second (same) username is still added to the file. I'm very confused.
Part I believe is not working -
If TextBox2.Text = temp Then
taken = True
End If
You should use String.Equals(String1, String) to compare TextBox text and the temp variable.
In your case it should be written as this.
If String.Equals(TextBox2.Text, temp) Then
taken = true
End if