Gestion de conflits d'accès concurrentielHandling Concurrency Conflicts

Note

Cette page décrit le fonctionnement de l’accès concurrentiel dans EF Core et comment gérer les conflits d’accès concurrentiel dans votre application.This page documents how concurrency works in EF Core and how to handle concurrency conflicts in your application. Consultez Jetons d’accès concurrentiel pour plus d’informations sur la configuration des jetons d’accès concurrentiel dans votre modèle.See Concurrency Tokens for details on how to configure concurrency tokens in your model.

Conseil

Vous pouvez afficher cet exemple sur GitHub.You can view this article's sample on GitHub.

L’accès concurrentiel à la base de données fait référence aux situations dans lesquelles plusieurs processus ou utilisateurs accèdent à ou modifient les mêmes données dans une base de données en même temps.Database concurrency refers to situations in which multiple processes or users access or change the same data in a database at the same time. Le Contrôle d’accès concurrentiel fait référence à des mécanismes spécifiques permettant de garantir la cohérence des données en présence de modifications simultanées.Concurrency control refers to specific mechanisms used to ensure data consistency in presence of concurrent changes.

EF Core implémente un contrôle d’accès concurrentiel optimiste, ce qui signifie qu’il permet à plusieurs processus ou utilisateurs d’apporter des modifications indépendamment sans surcharge de synchronisation ou de verrouillage.EF Core implements optimistic concurrency control, meaning that it will let multiple processes or users make changes independently without the overhead of synchronization or locking. Dans l’idéal, ces modifications n’interfèrent pas entre elles et sont donc en mesure de réussir.In the ideal situation, these changes will not interfere with each other and therefore will be able to succeed. Dans le pire des cas, deux ou processus ou plus tentent d’apporter des modifications en conflit, et seulement un d’eux doit réussir.In the worst case scenario, two or more processes will attempt to make conflicting changes, and only one of them should succeed.

Fonctionnement du contrôle d’accès concurrentiel dans EF CoreHow concurrency control works in EF Core

Les propriétés configurées en tant que jetons d’accès concurrentiel sont utilisées pour implémenter un contrôle d’accès concurrentiel optimiste : chaque fois qu’une opération de mise à jour ou de suppression est effectuée pendant SaveChanges, la valeur du jeton d’accès concurrentiel sur la base de données est comparée à la valeur d’origine lue par EF Core.Properties configured as concurrency tokens are used to implement optimistic concurrency control: whenever an update or delete operation is performed during SaveChanges, the value of the concurrency token on the database is compared against the original value read by EF Core.

  • Si les valeurs correspondent, l’opération peut s’effectuer.If the values match, the operation can complete.
  • Si les valeurs ne correspondent pas, EF Core suppose qu’un autre utilisateur a effectué une opération conflictuelle et abandonne la transaction en cours.If the values do not match, EF Core assumes that another user has performed a conflicting operation and aborts the current transaction.

Le cas où un autre utilisateur a effectué une opération en conflit avec l’opération en cours est appelé Conflit d’accès concurrentiel.The situation when another user has performed an operation that conflicts with the current operation is known as concurrency conflict.

Les fournisseurs de base de données sont responsables de l’implémentation de la comparaison des valeurs de jeton d’accès concurrentiel.Database providers are responsible for implementing the comparison of concurrency token values.

Sur les bases de données relationnelles, EF Core inclut une vérification de la valeur du jeton d’accès concurrentiel dans la clause WHERE de toute instruction UPDATE ou DELETE.On relational databases EF Core includes a check for the value of the concurrency token in the WHERE clause of any UPDATE or DELETE statements. Après l’exécution des instructions, EF Core lit le nombre de lignes affectées.After executing the statements, EF Core reads the number of rows that were affected.

Si aucune ligne n’est affectée, un conflit d’accès concurrentiel est détecté, et EF Core lève DbUpdateConcurrencyException.If no rows are affected, a concurrency conflict is detected, and EF Core throws DbUpdateConcurrencyException.

Par exemple, nous pouvons choisir de configurer LastName sur Person comme jeton d’accès concurrentiel.For example, we may want to configure LastName on Person to be a concurrency token. Toute opération de mise à jour sur la personne inclut alors le contrôle d’accès concurrentiel dans la clause WHERE :Then any update operation on Person will include the concurrency check in the WHERE clause:

UPDATE [Person] SET [FirstName] = @p1
WHERE [PersonId] = @p0 AND [LastName] = @p2;

Résolution des conflits d’accès concurrentielResolving concurrency conflicts

Dans la suite de l’exemple précédent, si un utilisateur tente d’enregistrer certaines modifications apportées à une Person, mais qu’un autre utilisateur a déjà modifié le LastName, une exception sera levée.Continuing with the previous example, if one user tries to save some changes to a Person, but another user has already changed the LastName the an exception will be thrown.

À ce stade, l’application peut simplement indiquer à l’utilisateur que la mise à jour a échoué en raison de modifications en conflit et passer à la suite.At this point, the application could simply inform the user that the update was not successful due to conflicting changes and move on. Mais il peut être souhaitable d’inviter l’utilisateur à s’assurer que cet enregistrement représente toujours la même personne réelle et à recommencer l’opération.But it may be desirable to prompt the user to ensure this record still represents the same actual person and to retry the operation.

Ce processus est un exemple de résolution d’un conflit d’accès concurrentiel.This process is an example of resolving a concurrency conflict.

La résolution d’un conflit d’accès concurrentiel consiste à fusionner les modifications en attente à partir du DbContext actuel avec les valeurs dans la base de données.Resolving a concurrency conflict involves merging the pending changes from the current DbContext with the values in the database. Les valeurs fusionnées varient en fonction de l’application et peuvent être contrôlées par saisie de l’utilisateur.What values get merged will vary based on the application and may be directed by user input.

Il existe trois ensembles de valeurs disponibles pour résoudre un conflit d’accès concurrentiel :There are three sets of values available to help resolve a concurrency conflict:

  • Les Valeurs actuelles sont les valeurs que l’application a tenté d’écrire dans la base de données.Current values are the values that the application was attempting to write to the database.

  • Les Valeurs d’origine sont les valeurs qui ont été récupérées à l’origine à partir de la base de données, avant que les modifications soient apportées.Original values are the values that were originally retrieved from the database, before any edits were made.

  • Les Valeurs de base de données sont les valeurs actuellement stockées dans la base de données.Database values are the values currently stored in the database.

L’approche générale pour gérer les conflits d’accès concurrentiel est la suivante :The general approach to handle a concurrency conflicts is:

  1. Interceptez DbUpdateConcurrencyException pendant SaveChanges.Catch DbUpdateConcurrencyException during SaveChanges.
  2. Utilisez DbUpdateConcurrencyException.Entries pour préparer un nouvel ensemble de modifications pour les entités concernées.Use DbUpdateConcurrencyException.Entries to prepare a new set of changes for the affected entities.
  3. Actualisez les valeurs d’origine du jeton d’accès concurrentiel pour refléter les valeurs actuelles dans la base de données.Refresh the original values of the concurrency token to reflect the current values in the database.
  4. Recommencez le processus jusqu'à ce qu’aucun conflit ne se produise.Retry the process until no conflicts occur.

Dans l’exemple suivant, Person.FirstName et Person.LastName sont configurés en tant que jetons d’accès concurrentiel.In the following example, Person.FirstName and Person.LastName are setup as concurrency tokens. Il existe un commentaire // TODO: à l’emplacement où vous incluez la logique spécifique de l’application pour choisir la valeur à enregistrer.There is a // TODO: comment in the location where you include application specific logic to choose the value to be saved.

using (var context = new PersonContext())
{
    // Fetch a person from database and change phone number
    var person = context.People.Single(p => p.PersonId == 1);
    person.PhoneNumber = "555-555-5555";

    // Change the person's name in the database to simulate a concurrency conflict
    context.Database.ExecuteSqlCommand(
        "UPDATE dbo.People SET FirstName = 'Jane' WHERE PersonId = 1");

    var saved = false;
    while (!saved)
    {
        try
        {
            // Attempt to save changes to the database
            context.SaveChanges();
            saved = true;
        }
        catch (DbUpdateConcurrencyException ex)
        {
            foreach (var entry in ex.Entries)
            {
                if (entry.Entity is Person)
                {
                    var proposedValues = entry.CurrentValues;
                    var databaseValues = entry.GetDatabaseValues();

                    foreach (var property in proposedValues.Properties)
                    {
                        var proposedValue = proposedValues[property];
                        var databaseValue = databaseValues[property];

                        // TODO: decide which value should be written to database
                        // proposedValues[property] = <value to be saved>;
                    }

                    // Refresh original values to bypass next concurrency check
                    entry.OriginalValues.SetValues(databaseValues);
                }
                else
                {
                    throw new NotSupportedException(
                        "Don't know how to handle concurrency conflicts for "
                        + entry.Metadata.Name);
                }
            }
        }
    }
}