Error 7878. The data has changed. (1 Viewer)

mafhobb

Registered User.
Local time
Yesterday, 18:09
Joined
Feb 28, 2006
Messages
1,245
Hello.

I have a database that has been in use for a month or so without issues but one. Once or twice a day I get "error 7878, the data has changed"

Let me explain how the database works; this is a customer support database where the user finds a the customer's record first and then adds calls/issues to it.

I believe that this error happens because two product support techs are accessing the same customer at the same time. In fact, I have had two computers side by side and I have been able to reproduce this error.

What are the basic database settings to prevent this form happening? The database is set to be opened "Shared", the default record locking is "No Locks" and the "Open databases by using record-level locking" check mark is checked. Are these the correct setting? is there anything else that I should take a look at?

Further, if these are correct then this must be a code problem, right?

Thanks

mafhobb
 

mafhobb

Registered User.
Local time
Yesterday, 18:09
Joined
Feb 28, 2006
Messages
1,245
Is there code to check and see if a record is being used by any user is a split database and then block the other users from using it until it is freed up?

mafhobb
 

gemma-the-husky

Super Moderator
Staff member
Local time
Today, 00:09
Joined
Sep 12, 2006
Messages
15,749
it isn't a code problem in particular. It's a very real problem of a any multi-user system - where more than one user needs access to a record at the same time

by default access uses a "locking" strategy called optimistic locking. (This is the no locks setting you saw) It assumes that a record will be safe to edit, unless it finds otherwise.
If you change the no locks setting to one of the other settings then you get pessimistic locking which I expand on below. It isn't as simple as just assuming that is an easy fix though - as it can cause as many or even more problems than it fixes.

record locking is an option to manage the situation with multiple "readers" and "writers". Readers are harmless, as any number of readers can access a record without problems. The problem is writers. If two writers get the same record, and both change it - then depending on the sequence, you can get inconsistent updates.

say a record has a value 4, and one user wants to add 5 to it, and another add 6. The correct result should be 15 - but if both users get the record when it is 4, one will make it 9, and the other make it 10, and neither will correctly make it 15.

now if you "lock" the record when the first user gets it - it stops the second user getting it - but it also stops all the "readers" getting it. If the user who locked the record leaves it locked (eg goes to lunch) everyone gets affected. It may prevent normal reports being run, and so on.

so with optimistic locking, instead of actively locking a record, access assumes the write will be Ok.

given the above scenario - user A gets a value of 4, adds 5, and sets the value to 9. user B gets the value of 9 adds 6 and sets the value to 15.

now if they both get the record when it is 4, what happens is this

users A gets 4, adds 5 and sets it to 9. User B also gets 4, adds 6 and tries to set it to 10.

BUT what happens when a record gets saved (ie changed) is that access re-reads the record first, to make sure it is the same as when you first read it. User A doesn't have a problem. The value is still 4, so the edit proceeds, and the value gets updated to 9. Now user B also read the initial value 4, and edited the value to 10 - but in his case the re-read finds that the record no longer contains 4 - it now contains a 9 - so now you get the "another user changed your data" message

It's a matter of taste whether you prefer

a) to get this message or
b) to actively implement a strategy that prevents users accessing records in the first place, with the attendant problems that can be generated by that method.

The problem with active locking strategies, as well as the problem described above of waiting for locks to be released - is the opposite to inconsistent updates and is called a deadly embrace. When you implement an active locking strategy you can get a situation where user A gets and locks record 1, and needs to get and lock another record 2. However user B might have already established a lock on record 2, and now needs record 1. So both users are stuck waiting for the other to finish. So you need a way of making one of these users abort their process, and backing out and releasing a lock and possibly undoing previous edits - which means your app needs very careful design. you may even have a circular chain of several users, each of which needs a resource held by another user. I never use active locking, but I think access does include retries, and time-out settings for this purpose. But in general, if you want locking it is best to design the app to require locks for the absolute least time necessary.

All in all, I prefer optimistic locking, and the occasional "another user" message.


just one other point. Record locking itself is often achieved by "page" locking. Memory is split into pages of various size. A record can exist on more than one page, and other records can use the same page your record is on. Locking records can also result in locally adjacent records being locked also - another reason to be careful about minimising the time you actually hold locks.

If you are desperate to lock records in your situation it might be easier to have an "inuse" flag within the record that can be set and unset by your programme, although sometimes such flags can be left in place accidentally, rather than use active record locking. Nobody said programming for multiple concurrent users was easy!
 
Last edited:

mafhobb

Registered User.
Local time
Yesterday, 18:09
Joined
Feb 28, 2006
Messages
1,245
That is a very throughout and clear explanation of the problem. Thank you GTH! I will keep this in mind with any future work.

Now I have kept running tests on how this happens and I feel I need to explain this better as it may not have anything to do with two users accessing the same data but with an actual problem with my code. Here is a more concise explanation for how this is happening in this particular case

I have a form with a textbox with an after update even:
Code:
Private Sub txtCustRepID_AfterUpdate()

'Go to Calls table and find original value for CustRepID
    'Fin the Call ID first
        Dim CallIDVar As Long
        Dim ContactIDVar As Long
        Dim CustRepIDOr As String
        CallIDVar = Forms![Contacts]![Call Listing Subform].Form![CallID]
        
    'find CustRepID Original based on CallID using a recordset on the Calls table, matching it to CallID
        Dim db As DAO.Database
        Dim rs As DAO.Recordset
        Set db = CurrentDb
        Set rs = db.OpenRecordset("Calls", dbOpenDynaset)
        rs.FindFirst "[CallID]=" & CallIDVar
    'Assign the value to the CustRepIDOr variable
        CustRepIDOr = rs!CustRepID
    'reset recordset
        Set db = Nothing
        Set rs = Nothing

    Dim sName As String
    Dim CustRepIDNew As String
        
    'get the new value
        CustRepIDNew = Me.txtCustRepID.Value
        
    'Are you sure you want to change it?
        If MsgBox("Sind Sie sich sicher die Belegnummer zu ändern?", vbQuestion + vbYesNo, "Händlerbelegnummer Wechseln") = vbNo Then
            'If entering the new value is cancelled
            Me.txtCustRepID.Value = CustRepIDOr
            Exit Sub
        Else
            'Who is making the changes
            sName = Nz(DLookup("EngineerID", "Tbl-Users", "PKEnginnerID = " & StrLoginName & ""), "")
            
            'Accept change and add new value to table
            CurrentDb.Execute _
            "UPDATE Calls " & _
            "SET CustRepID = '" & CustRepIDNew & "' " & _
            "WHERE CallID = " & CallIDVar, dbFailOnError
            
            'Add text to box below
            txtNewDetails = txtNewDetails & " Händlerbelegnummer wurde von " & CustRepIDOr & " zu " & CustRepIDNew & " geändert von " & sName & "."
        End If

End Sub

The error only happens when running the code below after the above code has been run. That is, the afterupdate event above does not err out by itself, it is the code below that returns the error after running the code above.

Code:
Private Sub cmdadddetails_Click()

' Dimensioning Variables
    Dim txtRecipients As String
    Dim txtSender As String
    Dim txtsubject As String
    Dim txtBody As String
    Dim StatusAfterContact As String
    Dim CurrNotes As String
    Dim ResolutionValue As String
    Dim sName As String
    Dim CustName As String
    Dim CustID As String
    Dim RMA As String
    Dim Trans As String
    Dim Email As String

   On Error GoTo cmdadddetails_Click_Error

    ResolutionValue = Nz(Me.lblResAfterAccept.Caption, "Rücksendung/Reparatur in Bearbeitung")
    

'Find if the user is a dealer/consumer and if this is a RV/HC call
    Dim CustDeal As String
    Dim RevHob As String
        CustDeal = Forms![frmRepair].[lblCustDeal].Caption
        RevHob = Forms![frmRepair].[lblRevHob].Caption
        
'verify if there is a customer name
    Me.txtCustName.SetFocus
    If txtCustName.Value = "" Or IsNull(txtCustName.Value) Then
        txtCustName.Value = "Kein Kundename vorhanden."
        MsgBox "Bitte beachten Sie, Kundendatei ist nicht vorhanden."
    Else
        CustName = txtCustName.Value
    End If

'verify if there is a customerID
    Me.txtCustID.SetFocus
    If txtCustID.Value = "" Or IsNull(txtCustID.Value) Then
        txtCustID.Value = "Keine Kundennummer."
        MsgBox "Bitte beachten Sie, Kundennummer ist nicht vorhanden."
    Else
        CustID = txtCustID.Value
    End If

'Verify if there is an e-mail
    Me.txteMail.SetFocus
    If txteMail.Value = "" Or IsNull(txteMail.Value) Then
        txteMail.Value = "Keine E-Mail Adresse angegeben."
        MsgBox "Bitte beachten Sie, der Kunde hat keine E-Mail Adresse angegeben."
    Else
        Email = txteMail.Value
    End If

'Check if text was actually entered.
If txtNewDetails.Value & "" = "" Then
    If MsgBox("Vorgang ohne weitere Einträge Bestätigen?", vbYesNo, "Keine zusätzlichen Informationen") = vbNo Then
        Exit Sub
    End If
End If

'Who is the user
sName = Nz(DLookup("EngineerID", "Tbl-Users", "PKEnginnerID = " & StrLoginName & ""), "")

'Find value of option frame optnewstatus and act acordingly
        StatusAfterContact = "Warten auf Sendung"
'       If the user is a customer "Endverbraucher" and not a distributor "Händler" then this
        If CustDeal = "Endverbraucher" Then
            Me.txtRMA.SetFocus
            If txtRMA.Value = "None" Then
                MsgBox "Bitte erst die Retoure genehmigen um eine RMA Nummer zu erhalten."
                Exit Sub
            End If
            RMA = txtRMA.Value

            Me.txtTrans.SetFocus
            If txtTrans.Value = "" Then
                MsgBox "Bitte die Retoure vorher Bestätigen um eine Vorgangsnummer zu erstellen und den Status Auszuwählen."
                Exit Sub
            End If
            Trans = txtTrans.Value
        
            DoCmd.OpenForm "frmEmailTemplate"
            With Forms("frmEmailTemplate")
                .txtFrom = emailfrom
                .txtTo = Email
                .txtSub = " Rücksendungs Informationen."
                .txtBod = " Sehr geehrte Damen und Herren, " & vbCrLf & vbCrLf & " senden Sie uns bitte den Artikel frei Haus ein, wir werden dann den Artikel frei Haus zurück Senden (Bei Unfreien Sendungen müssen wir 15.-€ in Rechnung stellen). Wir werden dann den Artikel auf Gewährleistung überprüfen. Bei einem Gewährleistungsanspruch erhalten Sie den Artikel Instandgesetzt bzw. Ausgetauscht kostenlos wieder zu geschickt. Sollte der defekt nicht unter die Gewährleistung fallen werden wir Sie darüber informieren und das weitere vorgehen absprechen. Bitte vermerken sie auf den Päckchen gut Lesbar die Rücksendenummer: " & _
                RMA & vbCrLf & vbCrLf & " Bitte senden Sie den Artikel an:" & vbCrLf & "                    Revell GmbH & Co. KG" & vbCrLf & "                    Henschelstraße 20-30" & vbCrLf & "                    32257 Bünde" & vbCrLf & vbCrLf & " Ihre Reklamation wird unter folgender Service Nummer (" & Trans & ") bearbeitet." & vbCrLf & vbCrLf & " Vielen dank" & vbCrLf & " Mit freundlichen Grüßen" & vbCrLf & Signature
            End With
        End If
'Check to see if a statusaftercontact was selected
If StatusAfterContact = "" Then
    MsgBox "Keinen Status ausgewählt."
    Exit Sub
End If

' Loading the SubCalls form in invisible mode
    DoCmd.OpenForm "SubCalls"
    [Forms]![SubCalls].Visible = False
    [Forms]![SubCalls]![CallID] = Forms![Contacts]![Call Listing Subform].Form![CallID].Value
    [Forms]![SubCalls]![SubCallDate] = Now()
    [Forms]![SubCalls]![WhoPickedUp] = sName
    [Forms]![SubCalls]![WhatWasSaid] = Me.txtNewDetails.Value
    [Forms]![SubCalls]![StatusAfterCall] = StatusAfterContact
    [Forms]![SubCalls]![ResolutionDetails] = ResolutionValue
    [Forms]![SubCalls]![Label] = ""
    DoCmd.Close acForm, "SubCalls"

'If the issue is any option other than "issue resolved" then enter the info to the notes text field.
'This empty string will sort out if the field is Null. Not the control.
    CurrNotes = Forms![Contacts]![Call Listing Subform].Form![Notes] & ""
'This should clear the resolution value on the call listing subform. It's a test!!!
'    Forms![contacts]![Call Listing Subform].Form![ResolutionDetails] = ""
'set focus on new details form
    Forms![frmRepair].SetFocus
'Assign values to other forms
    Forms![Contacts]![Call Listing Subform].Form![Notes] = " " & sName & " Schrieb am " & Now & " ----- " & Me.txtNewDetails.Value & ". " & vbCrLf & "Status des Vorgangs: " & StatusAfterContact & ". ******* End of Contact. *******" & vbCrLf & vbCrLf & CurrNotes
    'Test to add call Resolution details to this form. This resolution Details will be based on the absolute latest value entered for it.
    Forms![Contacts]![Call Listing Subform].Form![ResolutionDetails] = ResolutionValue
    ' end of test
    Forms![Contacts]![Call Listing Subform].Form.Refresh
    DoCmd.Close acForm, "frmRepair"
    Exit Sub

Is there anything here that you see that could cause this issue?

I mean, the form "subcalls" referenced in the code below is bound to the table "Calls" that is directly modified with the code above. Am I self-creating this problem with poor coding?

Thanks for the patience

mafhobb
 
Last edited:

gemma-the-husky

Super Moderator
Staff member
Local time
Today, 00:09
Joined
Sep 12, 2006
Messages
15,749
before I look, often the problem can be an error in coding. If a single user has a dirty/edited record, and opens the same record in a different form - then editing the record in the second instance will cause the same "another user" error - where you are the other user.
 

gemma-the-husky

Super Moderator
Staff member
Local time
Today, 00:09
Joined
Sep 12, 2006
Messages
15,749
ok

at the bottom of your code you have this

Code:
 Private Sub txtCustRepID_AfterUpdate()
 ...
 ...
            'Add text to box below
            txtNewDetails = txtNewDetails & " Händlerbelegnummer wurde von " & CustRepIDOr & " zu " & CustRepIDNew & " geändert von " & sName & "."
        End If
assuming txtNewDetails is bound to a field, then this statement is editing the record - which makes it dirty, and this may well be the issue.

the easiest way is perhaps to add recordselectors to your form temporarily - although they are useful anyway. If the record is dirty the black triangle will change to a pencil - so maybe doing that would help you identify the problem quickly.
 

mafhobb

Registered User.
Local time
Yesterday, 18:09
Joined
Feb 28, 2006
Messages
1,245
Hi. Thanks for the idea, but the bottom code has been working for five years without any issues. It is the top code that has recently been added to a new textbox on the same form. The error only happens if the bottom code is run after the top code. That is what I think that the top code is doing something that is causing the bottom code to error out.

Specifically, could this part of the code in the newly added textbox
Code:
            CurrentDb.Execute _
            "UPDATE Calls " & _
            "SET CustRepID = '" & CustRepIDNew & "' " & _
            "WHERE CallID = " & CallIDVar, dbFailOnError
cause the code that runs aftterwards and is related to the same "Calls" table (the form Subcalls is bound to the table "Calls") cause the problem?
Code:
' Loading the SubCalls form in invisible mode
    DoCmd.OpenForm "SubCalls"
    [Forms]![SubCalls].Visible = False
    [Forms]![SubCalls]![CallID] = Forms![Contacts]![Call Listing Subform].Form![CallID].Value
    [Forms]![SubCalls]![SubCallDate] = Now()
    [Forms]![SubCalls]![WhoPickedUp] = sName
    [Forms]![SubCalls]![WhatWasSaid] = Me.txtNewDetails.Value
    [Forms]![SubCalls]![StatusAfterCall] = StatusAfterContact
    [Forms]![SubCalls]![ResolutionDetails] = ResolutionValue
    [Forms]![SubCalls]![Label] = ""
    DoCmd.Close acForm, "SubCalls"

mafhobb
 

gemma-the-husky

Super Moderator
Staff member
Local time
Today, 00:09
Joined
Sep 12, 2006
Messages
15,749
yes, that was what I suggested

after you execute the code in the upper box in post #7 - (which looks fine) you then have an extra statement

Code:
txtNewDetails = txtNewDetails & " Händlerbelegnummer wurde von " & CustRepIDOr & " zu " & CustRepIDNew & " geändert von " & sName & "."

which may well be making the record dirty again (depending on what txtnewdetails is bound to.) If you add record selectors you will see this.
 

mafhobb

Registered User.
Local time
Yesterday, 18:09
Joined
Feb 28, 2006
Messages
1,245
OK, but unless I am not getting it, what you are suggesting is that this error happens at the afterupdate event and it has nothing to do with the second block of code.

This is not the case, the error does not happen in the after update even rather it happens when the cmdadddetails click event runs only if the afterupdate even has been run previously.

Also, txtnewdetails is only a textbox on the main form (not the pop up form that deals with the afterupdate textbox) so data is only displayed, not changed or saved.

mafhobb
 
Last edited:

mafhobb

Registered User.
Local time
Yesterday, 18:09
Joined
Feb 28, 2006
Messages
1,245
All right, this is now getting a bit stranger...

If I add stops to the code so I can go line by line to find the line that is returning the error, I do not get the error

If I add textboxes so I know where I am on the code as it goes through then I get no error.

It seems like there are two things in the code that happen to close to one another?
 

mafhobb

Registered User.
Local time
Yesterday, 18:09
Joined
Feb 28, 2006
Messages
1,245
Ok, the error happens here:
Code:
    Forms![Contacts]![Call Listing Subform].Form![Notes] = " " & sName & " Schrieb am " & Now & " ----- " & Me.txtNewDetails.Value & ". " & vbCrLf & "Status des Vorgangs: " & StatusAfterContact & ". ******* End of Contact. *******" & vbCrLf & vbCrLf & CurrNotes
    Forms![Contacts]![Call Listing Subform].Form![ResolutionDetails] = ResolutionValue
    Forms![Contacts]![Call Listing Subform].Form.Refresh
    DoCmd.Close acForm, "frmRepair"
    Exit Sub

If I add a msgbox before that then there is no error and everything works fine.

I have tried to add the delay below right before the code above but the error still shows up.
Code:
Dim Start As Variant
Start = Timer
Do While Timer < Start + 3
Loop

I am really confused now? Any ideas?

mafhobb
 

mafhobb

Registered User.
Local time
Yesterday, 18:09
Joined
Feb 28, 2006
Messages
1,245
I tweaked the delaying code a bit and it seems to work now.

Code:
Dim Start As Variant
Start = Timer
Do While Timer < Start + 0.5
DoEvents
Loop
 

gemma-the-husky

Super Moderator
Staff member
Local time
Today, 00:09
Joined
Sep 12, 2006
Messages
15,749
Glad you got it working. I just saw the extra line, and thought that might be a culprit, but it sounds like you identified the real cause.
 

mafhobb

Registered User.
Local time
Yesterday, 18:09
Joined
Feb 28, 2006
Messages
1,245
Thanks GTH, I would not say that I identified the culprit..I'd say I found a solution to a problem I do not understand...oh well, I guess that as long as it works...

mafhobb
 

Users who are viewing this thread

Top Bottom