Détection et résolution des conflits
Si vous traitez votre Recordset en mode immédiat, il y a beaucoup moins de chances que des problèmes d’accès concurrentiel se produisent. En revanche, si votre application utilise la mise à jour en mode batch, il se peut qu’un utilisateur modifie un enregistrement avant que les modifications apportées par un autre utilisateur modifient le même enregistrement. 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 qui envoie une mise à jour au serveur « gagne ». 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 de l’objet Recordset.
Notes
Lorsqu’ADO rencontre un conflit lors d’une mise à jour par lot, un avertissement est ajouté à la collection Errors. Par conséquent, vous devez toujours vérifier les erreurs immédiatement après avoir appelé BatchUpdate ; si vous en trouvez, commencez à tester l’hypothèse que vous avez rencontré un conflit. La première étape consiste à définir la propriété Filter sur l’objet Recordset égal à adFilterConflictingRecords. Cela limite l’affichage sur votre Recordset aux enregistrements qui sont en conflit. Si la propriété RecordCount est égale à zéro après cette étape, vous savez que l’erreur a été déclenché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 pouvant ê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 égal à 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 vous assurez que seuls les enregistrements visibles avec le paramètre de filtre actuel, autrement dit 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 un Recordset 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 (en conflit) 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 conserve la valeur d’origine du champ (la valeur qu’elle avait avant la dernière réussite de l’appel à UpdateBatch). Vous pouvez ensuite utiliser ces valeurs pour résoudre le conflit par programmation 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 à 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.