Détection et résolution des conflits
Si vous traitez votre Recordset en mode immédiat, les problèmes d’accès concurrentiel sont beaucoup moins probables. En revanche, si votre application utilise la mise à jour en mode par lot, il est possible qu'un utilisateur modifie un enregistrement avant que les modifications apportées par un autre utilisateur sur le même enregistrement soient enregistrées. Dans ce cas, vous souhaiterez que votre application gère correctement le conflit. Il peut s’agir de votre souhait que la dernière personne envoie une mise à jour au serveur « wins ». Vous pouvez également laisser l’utilisateur le plus récent décider quelle mise à jour doit être prioritaire en lui fournissant un choix entre les deux valeurs en conflit.
Quel que soit le cas, ADO fournit les propriétés UnderlyingValue et OriginalValue de l’objet Field pour gérer ces types de conflits. Utilisez ces propriétés en combinaison avec la méthode Resync et la propriété Filter du Recordset.
Remarques
Lorsque ADO rencontre un conflit lors d’une mise à jour par lots, un avertissement est ajouté à la collection Errors. Par conséquent, vous devez toujours rechercher des erreurs immédiatement après avoir appelé BatchUpdate et, si vous les trouvez, commencez à tester l’hypothèse que vous avez rencontré un conflit. La première étape consiste à définir la propriété Filter du Recordset égale à adFilterConflictingRecords. Cela limite l’affichage de votre recordset à ceux qui sont en conflit uniquement. Si la propriété RecordCount est égale à zéro après cette étape, vous savez que l’erreur a été générée par quelque chose d’autre qu’un conflit.
Lorsque vous appelez BatchUpdate, ADO et le fournisseur génèrent des instructions SQL pour effectuer des mises à jour sur la source de données. N’oubliez pas que certaines sources de données ont des limitations sur les types de colonnes qui peuvent être utilisés dans une clause WHERE.
Ensuite, appelez la méthode Resync sur l’objet Recordset avec l’argument AffectRecords défini sur adAffectGroup et l’argument ResyncValues défini sur adResyncUnderlyingValues. La méthode Resync met à jour les données de l’objet Recordset actif à partir de la base de données sous-jacente. En utilisant adAffectGroup, vous assurez que seuls les enregistrements visibles avec le paramètre de filtre actuel, autrement dit, seuls les enregistrements en conflit sont resynchronisés avec la base de données. Cela peut faire une différence significative en matière de performances si vous traitez d’un jeu d’enregistrements volumineux. En définissant l’argument ResyncValues sur adResyncUnderlyingValues lors de l’appel de Resync, vous assurez que la propriété UnderlyingValue contient la valeur (conflictuelle) de la base de données, que la propriété Value conserve la valeur entrée par l’utilisateur et que la propriété OriginalValue contiendra la valeur d’origine du champ (la valeur qu’elle avait avant la dernière réussite de l’appel UpdateBatch a été effectuée). Vous pouvez ensuite utiliser ces valeurs pour résoudre le conflit par programme ou exiger que l’utilisateur sélectionne la valeur qui sera utilisée.
Cette technique est illustrée dans l’exemple de code suivant. L’exemple crée artificiellement un conflit à l’aide d’un recordset distinct pour modifier une valeur dans la table sous-jacente avant l’appel de UpdateBatch.
'BeginConflicts
strConn = "Provider=SQLOLEDB;Initial Catalog=Northwind;" & _
"Data Source=MySQLServer;Integrated Security=SSPI;"
strSQL = "SELECT * FROM Shippers WHERE ShipperID = 2"
'Open Rs and change a value
Set objRs1 = New ADODB.Recordset
Set objRs2 = New ADODB.Recordset
objRs1.CursorLocation = adUseClient
objRs1.Open strSQL, strConn, adOpenStatic, adLockBatchOptimistic, adCmdText
objRs1("Phone") = "(111) 555-1111"
'Introduce a conflict at the db...
objRs2.Open strSQL, strConn, adOpenKeyset, adLockOptimistic, adCmdText
objRs2("Phone") = "(999) 555-9999"
objRs2.Update
objRs2.Close
Set objRs2 = Nothing
On Error Resume Next
objRs1.UpdateBatch
If objRs1.ActiveConnection.Errors.Count <> 0 Then
Dim intConflicts As Integer
intConflicts = 0
objRs1.Filter = adFilterConflictingRecords
intConflicts = objRs1.RecordCount
'Resync so we can see the UnderlyingValue and offer user a choice.
'This sample only displays all three values and resets to original.
objRs1.Resync adAffectGroup, adResyncUnderlyingValues
If intConflicts > 0 Then
strMsg = "A conflict occurred with updates for " & intConflicts & _
" record(s)." & vbCrLf & "The values will be restored" & _
" to their original values." & vbCrLf & vbCrLf
objRs1.MoveFirst
While Not objRs1.EOF
strMsg = strMsg & "SHIPPER = " & objRs1("CompanyName") & vbCrLf
strMsg = strMsg & "Value = " & objRs1("Phone").Value & vbCrLf
strMsg = strMsg & "UnderlyingValue = " & _
objRs1("Phone").UnderlyingValue & vbCrLf
strMsg = strMsg & "OriginalValue = " & _
objRs1("Phone").OriginalValue & vbCrLf
strMsg = strMsg & vbCrLf & "Original value has been restored."
MsgBox strMsg, vbOKOnly, _
"Conflict " & objRs1.AbsolutePosition & _
" of " & intConflicts
objRs1("Phone").Value = objRs1("Phone").OriginalValue
objRs1.MoveNext
Wend
objRs1.UpdateBatch adAffectGroup
Else
'Other error occurred. Minimal handling in this example.
strMsg = "Errors occurred during the update. " & _
objRs1.ActiveConnection.Errors(0).Number & " " & _
objRs1.ActiveConnection.Errors(0).Description
End If
On Error GoTo 0
End If
objRs1.MoveFirst
objRs1.Close
Set objRs1 = Nothing
'EndConflicts
Vous pouvez utiliser la propriété Status de l’enregistrement actif ou d’un champ spécifique pour déterminer le type de conflit qui s’est produit.
Pour plus d’informations sur la gestion des erreurs, consultez gestion des erreurs.