Comment gérer les conflits d'accès concurrentiel dans Entity Framework

La gestion de la concurrence peut être utilisée pour maintenir l'intégrité et la cohérence des données lorsque plusieurs utilisateurs accèdent simultanément à la même ressource. Des violations de concurrence peuvent se produire lorsque vous avez des transactions interdépendantes, c'est-à-dire des transactions qui dépendent les unes des autres et qui tentent d'accéder à la même ressource.

Gestion des conflits d'accès concurrentiel dans Entity Framework

Voyons maintenant comment chacune de ces stratégies fonctionne dans Entity Framework. Dans la concurrence pessimiste, lorsqu'un enregistrement particulier est mis à jour, toutes les autres mises à jour simultanées sur le même enregistrement seront mises en attente jusqu'à ce que l'opération en cours soit terminée et que le contrôle soit abandonné afin que d'autres opérations simultanées puissent continuer. Dans le mode de concurrence optimiste, le dernier enregistrement enregistré "gagne". Dans ce mode, on suppose que les conflits de ressources dus à des accès simultanés à une ressource partagée sont peu probables, mais pas impossibles.

Incidemment, Entity Framework prend en charge la concurrence optimiste par défaut. Entity Framework ne prend pas en charge la concurrence pessimiste prête à l'emploi. Voyons maintenant comment Entity Framework résout les conflits de concurrence lorsque vous travaillez dans la concurrence optimiste (mode par défaut).

Lorsque vous travaillez avec un mode de gestion de la concurrence optimiste, vous souhaitez généralement enregistrer les données dans votre base de données en supposant que les données n'ont pas changé depuis leur chargement dans la mémoire. Notez que lorsque vous tentez d'enregistrer des modifications dans la base de données à l'aide de la méthode SaveChanges sur votre instance de contexte de données, une exception DbUpdateConcurrencyException est levée. Voyons maintenant comment nous pouvons résoudre ce problème.

Pour vérifier la violation de la concurrence, vous pouvez inclure un champ dans votre classe d'entité et le marquer à l'aide de l'attribut Horodatage. Reportez-vous à la classe d'entité donnée ci-dessous.

public class Author

   {

       public Int32 Id { get; set; }

       public string FirstName { get; set; }

       public string LastName { get; set; }

       public string Address { get; set; }

       [Timestamp]

       public byte[] RowVersion { get; set; }

   }

Désormais, Entity Framework prend en charge deux modes de concurrence: Aucun et Fixe. Alors que le premier implique qu'aucune vérification de concurrence ne serait effectuée lors de la mise à jour de l'entité, le second implique que la valeur d'origine de la propriété sera prise en compte lors de l'exécution des clauses WHERE au moment où les mises à jour ou les suppressions de données sont effectuées. Si vous avez une propriété marquée à l'aide de l'horodatage, le mode d'accès concurrentiel est considéré comme fixe, ce qui implique à son tour que la valeur d'origine de la propriété serait prise en compte dans la clause WHERE de toute mise à jour ou suppression de données pour cette entité particulière.

Pour résoudre les conflits de concurrence optimiste, vous pouvez tirer parti de la méthode Reload pour mettre à jour les valeurs actuelles de votre entité résidant dans la mémoire avec les valeurs récentes de la base de données. Une fois rechargé avec les données mises à jour, vous pouvez tenter à nouveau de conserver votre entité dans la base de données. L'extrait de code suivant illustre comment cela peut être réalisé.

using (var dbContext = new IDBDataContext())

{

     Author author = dbContext.Authors.Find(12);

     author.Address = "Hyderabad, Telengana, INDIA";

       try

         {

             dbContext.SaveChanges();

         }

         catch (DbUpdateConcurrencyException ex)

         {

             ex.Entries.Single().Reload();

             dbContext.SaveChanges();

         }

}

Notez que vous pouvez tirer parti de la méthode Entries sur l'instance DbUpdateConcurrencyException pour récupérer la liste des instances DbEntityEntry correspondant aux entités qui n'ont pas pu être mises à jour lorsqu'une méthode SaveChanges a été appelée pour conserver les entités dans la base de données.

Or, l'approche dont nous venons de parler est souvent appelée «gains stockés» ou «victoires de base de données» puisque les données contenues dans l'entité sont écrasées par les données disponibles dans la base de données. Vous pouvez également suivre une autre approche appelée «client gagne». Dans cette stratégie, les données de la base de données sont extraites pour peupler l'entité. En substance, les données extraites de la base de données sous-jacente sont définies comme valeurs d'origine pour l'entité. L'extrait de code suivant illustre comment cela peut être réalisé.

try

{

     dbContext.SaveChanges();

}

catch (DbUpdateConcurrencyException ex)

{

   var data = ex.Entries.Single();

   data.OriginalValues.SetValues(data.GetDatabaseValues());

}

Vous pouvez également vérifier si l'entité que vous essayez de mettre à jour est déjà supprimée par un autre utilisateur ou a déjà été mise à jour par un autre utilisateur. L'extrait de code suivant montre comment procéder.

catch (DbUpdateConcurrencyException ex)

{

   var entity = ex.Entries.Single().GetDatabaseValues();

   if (entity == null)

   {

         Console.WriteLine("The entity being updated is already deleted by another user...");

   }

   else

   {

         Console.WriteLine("The entity being updated has already been updated by another user...");

   }

}

Si votre table de base de données n'a pas de colonne d'horodatage ou de version de ligne, vous pouvez tirer parti de l'attribut ConcurrencyCheck pour détecter les conflits de concurrence lors de l'utilisation d'Entity Framework. Voici comment cette propriété est utilisée.

[Table("Authors"]

public class Author

{

   public Author() {}

   [Key]

   public int Id { get; set; }

   [ConcurrencyCheck]

   public string FirstName { get; set; }

   public string LastName { get; set; }

   public string Address { get; set; }

}

Ce faisant, SQL Server inclurait automatiquement AuthorName lors de l'exécution des instructions de mise à jour ou de suppression dans la base de données.