Partager via


Accès concurrentiel optimiste

Dans un environnement multi-utilisateur, il existe deux modèles pour la mise à jour de données dans une base de données : l'accès concurrentiel optimiste et l'accès concurrentiel pessimiste. L'objet DataSet est conçu pour privilégier l'utilisation de l'accès concurrentiel optimiste pour les activités longues, comme lorsque vous accédez à des données distantes ou lorsque des utilisateurs interagissent avec des données.

L'accès concurrentiel pessimiste implique le verrouillage de lignes à la source de données pour empêcher que les utilisateurs modifient des données d'une manière susceptible d'affecter d'autres utilisateurs. Dans un modèle pessimiste, lorsqu'un utilisateur effectue une action entraînant l'application d'un verrou, les autres utilisateurs ne peuvent pas effectuer d'actions qui créeraient un conflit avec le verrou tant que le propriétaire de ce dernier ne l'a pas libéré. Ce modèle est principalement utilisé dans les environnements où les conflits relatifs aux données sont fréquents et où le coût de la protection des données par verrous est inférieur à celui de la restauration des transactions en cas de conflits d'accès concurrentiel.

Par conséquent, dans un modèle d'accès concurrentiel pessimiste, un utilisateur qui lit une ligne dans l'intention de la modifier crée un verrou. Jusqu'à ce que cet utilisateur ait terminé sa mise à jour et libéré le verrou, personne d'autre ne peut modifier cette ligne. C'est pourquoi il est préférable d'implémenter l'accès concurrentiel pessimiste lorsque les temps de verrouillage sont courts, comme c'est le cas pour le traitement d'enregistrements par programme. L'accès concurrentiel pessimiste ne constitue pas la solution la plus adaptée lorsque des utilisateurs interagissent avec les données, et posent des verrous pendant des laps de temps relativement longs.

Au contraire, les utilisateurs qui ont recours à un accès concurrentiel optimiste ne verrouillent pas une ligne lorsqu'ils la lisent. Lorsqu'un utilisateur souhaite mettre à jour une ligne, l'application doit déterminer si un autre utilisateur a modifié cette ligne depuis sa dernière lecture. L'accès concurrentiel optimiste est généralement utilisé dans les environnements où les conflits relatifs aux données sont rares. Cela améliore les performances, dans la mesure où aucun verrouillage des enregistrements n'est requis, sachant que le verrouillage d'enregistrements nécessite des ressources serveur supplémentaires. Il faut également savoir que la gestion des verrous d'enregistrements requiert une connexion permanente au serveur de base de données. Parce que ce n'est pas le cas dans un modèle d'accès concurrentiel optimiste, les connexions au serveur sont disponibles pour traiter plus rapidement un nombre important de clients.

Dans un modèle d'accès concurrentiel optimiste, une violation est réputée avoir eu lieu si, après réception par un utilisateur d'une valeur provenant de la base de données, un autre utilisateur modifie cette valeur avant que le premier n'ait tenté de le faire.

Les tableaux ci-dessous suivent un exemple d'accès concurrentiel optimiste.

À 13h00, l'utilisateur 1 lit une ligne de la base de données contenant les valeurs suivantes :

IDClient     Nom     Prénom

101          Smith             Bob

Nom de la colonne Valeur d'origine Valeur actuelle Valeur dans la base de données
IDClient 101 101 101
Nom Smith Smith Smith
Prénom Bob Bob Bob

À 13h01, l'utilisateur 2 lit la même ligne.

À 13h03, l'utilisateur 2 modifie la valeur de Prénom pour remplacer « Bob » par « Robert » et met à jour la base de données.

Nom de la colonne Valeur d'origine Valeur actuelle Valeur dans la base de données
IDClient 101 101 101
Nom Smith Smith Smith
Prénom Bob Robert Bob

La mise à jour réussit, car les valeurs de la base de données au moment de la mise à jour correspondent aux valeurs d'origine dont dispose l'utilisateur 2.

À 13h05, l'utilisateur 1 modifie la valeur de Prénom pour remplacer « Bob » par « James » et tente de mettre à jour la base de données.

Nom de la colonne Valeur d'origine Valeur actuelle Valeur dans la base de données
IDClient 101 101 101
Nom Smith Smith Smith
Prénom Bob James Robert

À ce stade, l'utilisateur est confronté à une violation d'accès concurrentiel optimiste, car les valeurs de la base de données ne correspondent plus aux valeurs d'origine qu'attendait l'utilisateur 1. Il convient maintenant de décider si les modifications apportées par l'utilisateur 2 devront être remplacées par celles de l'utilisateur 1 ou si ces dernières devront être annulées.

Recherche des violations d'accès concurrentiel optimiste

Il existe plusieurs techniques qui permettent de déceler la présence d'une violation d'accès concurrentiel optimiste. L'une d'entre elles consiste à inclure une colonne horodateur dans la table. Les bases de données proposent généralement une fonctionnalité d'horodatage qui peut être utilisée pour connaître la date et l'heure de la dernière mise à jour d'un enregistrement. En utilisant cette technique, une colonne horodateur est incluse dans la définition de la table. Chaque fois que l'enregistrement est mis à jour, l'horodatage est également mis à jour en fonction de la date et de l'heure actuelles. Dans un test visant à déceler la présence de violations d'accès concurrentiel optimiste, la colonne horodateur est retournée avec toute requête liée au contenu de la table. Lors d'une tentative de mise à jour, la valeur d'horodatage figurant dans la base de données est comparée à la valeur d'origine contenue dans la ligne modifiée. Si les valeurs correspondent, la modification est apportée et la colonne horodateur est mise à jour en fonction de la date et de l'heure actuelles afin de refléter la modification. Si elles ne correspondent pas, une violation d'accès concurrentiel optimiste s'est produite.

Une autre technique de recherche des violations d'accès concurrentiel optimiste consiste à vérifier que toutes les valeurs de colonne d'origine d'une ligne correspondent toujours à celles qui figurent dans la base de données. Examinons, par exemple, la requête suivante :

SELECT Col1, Col2, Col3 FROM Table1

Pour savoir s'il y a eu violation d'accès concurrentiel optimiste lors de la mise à jour d'une ligne de Table1, vous pouvez écrire l'instruction UPDATE suivante :

UPDATE Table1 Set Col1 = @NewCol1Value,
              Set Col2 = @NewCol2Value,
              Set Col3 = @NewCol3Value
WHERE Col1 = @OldCol1Value AND
      Col2 = @OldCol2Value AND
      Col3 = @OldCol3Value

Tant que les valeurs d'origine correspondent à celles qui figurent dans la base de données, la mise à jour est effectuée. Si une valeur a été modifiée, la mise à jour ne modifiera pas la ligne, car la clause WHERE ne trouvera pas de correspondance.

Notez qu'il est recommandé de toujours retourner une valeur de clé primaire unique dans votre requête. Sinon, l'instruction UPDATE précédente risque de mettre à jour plusieurs lignes, ce qui n'est pas nécessairement dans vos intentions.

Si une colonne de votre source de données accepte les valeurs nulles, vous devrez peut-être étendre votre clause WHERE pour vérifier s'il existe une référence null dans votre table locale et sa correspondance dans votre source de données. Par exemple, l'instruction UPDATE suivante vérifie qu'une référence null de la ligne locale correspond toujours à une référence null dans la source de données, ou si la valeur de la ligne locale correspond toujours à celle de la source de données.

UPDATE Table1 Set Col1 = @NewVal1
  WHERE (@OldVal1 IS NULL AND Col1 IS NULL) OR Col1 = @OldVal1

Vous pouvez aussi choisir d'appliquer des critères moins restrictifs lorsque vous utilisez un modèle d'accès concurrentiel optimiste. Par exemple, utiliser uniquement les colonnes clés primaires dans la clause WHERE aboutit au remplacement des données, que les autres colonnes aient ou non subi une mise à jour depuis la dernière requête. Vous pouvez aussi appliquer une clause WHERE à certaines colonnes uniquement, ce qui aura pour effet de remplacer les données, sauf si des champs spécifiques ont été mis à jour depuis la dernière requête les concernant.

DataAdapter.RowUpdated, événement

L'événement DataAdapter.RowUpdated peut être utilisé avec les techniques précédemment décrites, afin d'avertir votre application en cas de violation d'accès concurrentiel optimiste. RowUpdated intervient après chaque tentative de mise à jour d'une ligne modifiée provenant d'un DataSet. Cela vous permet d'ajouter un code de gestion spécial, qui traitera les exceptions le cas échéant, ajoutera des informations d'erreur personnalisées, ajoutera une logique pour les nouvelles tentatives, etc. L'objet RowUpdatedEventArgs retourne une propriété RecordsAffected faisant ressortir, pour une ligne modifiée dans une table, le nombre de lignes affectées par une commande de mise à jour donnée. En configurant la commande de mise à jour de sorte qu'elle teste l'accès concurrentiel optimiste, la propriété RecordsAffected retourne la valeur 0 si une violation d'accès concurrentiel optimiste s'est produite, puisque aucun enregistrement n'a été mis à jour. Dans ce cas, une exception est levée. L'événement RowUpdated vous permet de gérer ce cas de figure et d'éviter l'exception en définissant une valeur RowUpdatedEventArgs.Status appropriée, telle que UpdateStatus.SkipCurrentRow. Pour plus d'informations sur l'événement RowUpdated, consultez Utilisation des événements du DataAdapter.

Vous pouvez éventuellement définir DataAdapter.ContinueUpdateOnError à true, avant d'appeler Update, et répondre aux informations d'erreur stockées dans la propriété RowError d'une ligne donnée lorsque Update est terminée. Pour plus d'informations, consultez Ajout et lecture des informations sur les erreurs de ligne.

Exemple d'accès concurrentiel optimiste

L'exemple qui suit définit le UpdateCommand d'un DataAdapter pour tester l'accès concurrentiel optimiste, puis utilise l'événement RowUpdated pour déceler la présence de violations d'accès concurrentiel optimiste. En cas de violation d'accès concurrentiel optimiste, l'application définit le RowError de la ligne pour laquelle la mise à jour a été demandée afin de refléter une violation d'accès concurrentiel optimiste.

Notez que les valeurs du paramètre passé à la clause WHERE de la commande UPDATE sont mappées aux valeurs d'origine de leur colonne.

  Dim nwindConn As SqlConnection = New SqlConnection("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=northwind")

  Dim custDA As SqlDataAdapter = New SqlDataAdapter("SELECT CustomerID, CompanyName FROM Customers ORDER BY CustomerID", nwindConn)

  ' The Update command checks for optimistic concurrency violations in the WHERE clause.
  custDA.UpdateCommand = New SqlCommand("UPDATE Customers (CustomerID, CompanyName) VALUES(@CustomerID, @CompanyName) " & _
                                        "WHERE CustomerID = @oldCustomerID AND CompanyName = @oldCompanyName", nwindConn)
  custDA.UpdateCommand.Parameters.Add("@CustomerID", SqlDbType.NChar, 5, "CustomerID")
  custDA.UpdateCommand.Parameters.Add("@CompanyName", SqlDbType.NVarChar, 30, "CompanyName")

  ' Pass the original values to the WHERE clause parameters.
  Dim myParm As SqlParameter
  myParm = custDA.UpdateCommand.Parameters.Add("@oldCustomerID", SqlDbType.NChar, 5, "CustomerID")
  myParm.SourceVersion = DataRowVersion.Original
  myParm = custDA.UpdateCommand.Parameters.Add("@oldCompanyName", SqlDbType.NVarChar, 30, "CompanyName")
  myParm.SourceVersion = DataRowVersion.Original

  ' Add the RowUpdated event handler.
  AddHandler custDA.RowUpdated, New SqlRowUpdatedEventHandler(AddressOf OnRowUpdated)

  Dim custDS As DataSet = New DataSet()
  custDA.Fill(custDS, "Customers")

  ' Modify the DataSet contents.

  custDA.Update(custDS, "Customers")

  Dim myRow As DataRow

  For Each myRow In custDS.Tables("Customers").Rows
    If myRow.HasErrors Then Console.WriteLine(myRow(0) & vbCrLf & myRow.RowError)
  Next


Private Shared Sub OnRowUpdated(sender As object, args As SqlRowUpdatedEventArgs)
  If args.RecordsAffected = 0
    args.Row.RowError = "Optimistic Concurrency Violation Encountered"
    args.Status = UpdateStatus.SkipCurrentRow
  End If
End Sub
[C#]
  SqlConnection nwindConn = new SqlConnection("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=northwind");

  SqlDataAdapter custDA = new SqlDataAdapter("SELECT CustomerID, CompanyName FROM Customers ORDER BY CustomerID", nwindConn);

  // The Update command checks for optimistic concurrency violations in the WHERE clause.
  custDA.UpdateCommand = new SqlCommand("UPDATE Customers (CustomerID, CompanyName) VALUES(@CustomerID, @CompanyName) " +
                                        "WHERE CustomerID = @oldCustomerID AND CompanyName = @oldCompanyName", nwindConn);
  custDA.UpdateCommand.Parameters.Add("@CustomerID", SqlDbType.NChar, 5, "CustomerID");
  custDA.UpdateCommand.Parameters.Add("@CompanyName", SqlDbType.NVarChar, 30, "CompanyName");

  // Pass the original values to the WHERE clause parameters.
  SqlParameter myParm;
  myParm = custDA.UpdateCommand.Parameters.Add("@oldCustomerID", SqlDbType.NChar, 5, "CustomerID");
  myParm.SourceVersion = DataRowVersion.Original;
  myParm = custDA.UpdateCommand.Parameters.Add("@oldCompanyName", SqlDbType.NVarChar, 30, "CompanyName");
  myParm.SourceVersion = DataRowVersion.Original;

  // Add the RowUpdated event handler.
  custDA.RowUpdated += new SqlRowUpdatedEventHandler(OnRowUpdated);

  DataSet custDS = new DataSet();
  custDA.Fill(custDS, "Customers");

  // Modify the DataSet contents.

  custDA.Update(custDS, "Customers");

  foreach (DataRow myRow in custDS.Tables["Customers"].Rows)
  {
    if (myRow.HasErrors)
      Console.WriteLine(myRow[0] + "\n" + myRow.RowError);
  }


protected static void OnRowUpdated(object sender, SqlRowUpdatedEventArgs args)
{
  if (args.RecordsAffected == 0) 
  {
    args.Row.RowError = "Optimistic Concurrency Violation Encountered";
    args.Status = UpdateStatus.SkipCurrentRow;
  }
}

Voir aussi

Exemples de scénarios ADO.NET | Mise à jour de la base de données avec un DataAdapter et le DataSet | Utilisation des événements du DataAdapter | Ajout et lecture des informations sur les erreurs de ligne | Accès aux données avec ADO.NET | Utilisation des fournisseurs de données .NET Framework pour l'accès aux données