Come gestire i conflitti di concorrenza in Entity Framework

La gestione della concorrenza può essere utilizzata per mantenere l'integrità e la coerenza dei dati quando più utenti accedono contemporaneamente alla stessa risorsa. Le violazioni della concorrenza possono verificarsi quando si hanno transazioni interdipendenti, cioè transazioni che dipendono l'una dall'altra e si tenta di accedere alla stessa risorsa.

Gestione dei conflitti di concorrenza in Entity Framework

Vediamo ora come funzionano ognuna di queste strategie in Entity Framework. Nella concorrenza pessimistica, quando un particolare record viene aggiornato, tutti gli altri aggiornamenti simultanei sullo stesso record verranno sospesi fino al completamento dell'operazione corrente e al rilascio del controllo in modo che altre operazioni simultanee possano continuare. Nella modalità di concorrenza ottimistica, l'ultimo record salvato "vince". In questa modalità, si presume che i conflitti di risorse dovuti ad accessi simultanei a una risorsa condivisa siano improbabili, ma non impossibili.

Per inciso, Entity Framework fornisce il supporto per la concorrenza ottimistica per impostazione predefinita. Entity Framework non fornisce il supporto per la concorrenza pessimistica pronta all'uso. Vediamo ora come Entity Framework risolve i conflitti di concorrenza quando si lavora nella concorrenza ottimistica (modalità predefinita).

Quando si lavora con la modalità di gestione della concorrenza ottimistica, in genere si desidera salvare i dati nel database presumendo che i dati non siano cambiati da quando sono stati caricati in memoria. Si noti che quando si tenta di salvare le modifiche al database utilizzando il metodo SaveChanges sull'istanza del contesto dati, verrà generata un'eccezione DbUpdateConcurrencyException. Vediamo ora come possiamo risolvere questo problema.

Per verificare la violazione della concorrenza, puoi includere un campo nella tua classe di entità e contrassegnarlo utilizzando l'attributo Timestamp. Fare riferimento alla classe di entità fornita di seguito.

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; }

   }

Ora, Entity Framework supporta due modalità di concorrenza: nessuna e fissa. Mentre il primo implica che non verrebbero eseguiti controlli di concorrenza durante l'aggiornamento dell'entità, il secondo implica che il valore originale della proprietà verrà considerato durante l'esecuzione delle clausole WHERE nel momento in cui vengono eseguiti gli aggiornamenti o l'eliminazione dei dati. Se si dispone di una proprietà contrassegnata utilizzando Timestamp, la modalità di concorrenza è considerata fissa, il che a sua volta implica che il valore originale della proprietà sarebbe considerato nella clausola WHERE di qualsiasi aggiornamento o eliminazione dei dati per quella particolare entità.

Per risolvere i conflitti di concorrenza ottimistica, puoi sfruttare il metodo Reload per aggiornare i valori correnti nell'entità che risiede nella memoria con i valori recenti nel database. Una volta ricaricato con i dati aggiornati, puoi provare a persistere nuovamente la tua entità nel database. Il frammento di codice seguente illustra come ottenere questo risultato.

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();

         }

}

Tieni presente che puoi sfruttare il metodo Entries sull'istanza DbUpdateConcurrencyException per recuperare l'elenco di istanze DbEntityEntry corrispondenti alle entità che non è stato possibile aggiornare quando è stato chiamato un metodo SaveChanges per rendere persistenti le entità nel database.

Ora, l'approccio che abbiamo appena discusso è spesso chiamato "vittorie archiviate" o "vittorie del database" poiché i dati contenuti nell'entità vengono sovrascritti dai dati disponibili nel database. Puoi anche seguire un altro approccio chiamato "cliente vince". In questa strategia, i dati dal database vengono recuperati per popolare l'entità. In sostanza, i dati recuperati dal database sottostante sono impostati come valori originali per l'entità. Il frammento di codice seguente illustra come ottenere questo risultato.

try

{

     dbContext.SaveChanges();

}

catch (DbUpdateConcurrencyException ex)

{

   var data = ex.Entries.Single();

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

}

Puoi anche verificare se l'entità che stai tentando di aggiornare è già stata eliminata da un altro utente o è già stata aggiornata da un altro utente. Il frammento di codice seguente illustra come eseguire questa operazione.

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...");

   }

}

Se la tabella del database non ha una colonna timestamp o rowversion, puoi sfruttare l'attributo ConcurrencyCheck per rilevare i conflitti di concorrenza quando si usa Entity Framework. Ecco come viene utilizzata questa proprietà.

[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; }

}

In tal modo, SQL Server includerà automaticamente AuthorName durante l'esecuzione di istruzioni di aggiornamento o eliminazione nel database.