Concurrency handling in Entity Framework

By Mirek on (tags: database concurrency, Entity Framework, categories: code)

In this post I am going to present my solution for handling optimistic concurrency scenarios in Entity Framework. This solution is somehow an extension of recommended approaches presented on MSDN Entity Framework learning  center here.

Apart from pessimistic concurrency pattern, which assumes creating read or update locks on data when it is edited, this approach concerns on optimistic concurrency pattern.
Optimistic pattern is based on handling concurrency conflicts at the moment that user tries to update data. Basically an concurrency conflict occurs when two users simultaneously work on a portion of data not knowing about it. So for instance user A loads product and starts editing it in his client application. A while later user B loads the same product, changes some of its properties and saves it to the database. Now user A, which is still working on its copy of product, does not know anything about the changes made by user B. Now concurrency conflict occurs when he tries to save modified product to the database. The problem also occurs when user B deletes the product while user A is still editing it.
The solution I am going to present is about to handle such conflicts at the moment the user A tries to store his copy of product.

Before we start handling the conflicts, we have to let the EF detect when the conflict occurs. There are two options.
First is pointing out the properties that have to be compared in order to detect if entity was modified by another user. We need to mark those properties with ConcurrencyCheck flag. This will make the EF engine to compare the original values of those properties with the values in database. If values are different then it means the entity was modified or deleted by concurrent user and the concurrency exception will be thrown.
However the preferred way of detecting concurrency changes is the TimeStamp field, which is updated to an new unique value every time the entity is modified. EF then compares it on every update and detects the conflict this way.
In presented solution all entities are derived from a ConcurrencyEntity

public abstract class ConcurrentEntity : Entity
{
    [Timestamp]
    public byte[] RowVersion { get; set; }    
}

We also need some constants.

public enum DbConcurrenceConflict
{
    /// <summary>
    /// No concurrene conflict has been detected
    /// </summary>
    None,
    /// <summary>
    /// Entity has been deleted by other user
    /// </summary>
    EntityDeleted,
    /// <summary>
    /// Entity has been modified by other user
    /// </summary>
    EntityModified
}

Above enumeration informs if the conflicted entity was deleted or modified.

public enum DbConcurrenceStrategy
{
    /// <summary>
    /// Ask user to react in case of concurrence conflict
    /// </summary>
    None,
    /// <summary>
    /// Merge strategy - changes made to entity properties are stored in database
    /// if and only if same properties were not changed by other user
    /// </summary>
    Merge,
    /// <summary>
    /// Client wins strategy, all changes made to database 
    /// are overwriten by current user values
    /// </summary>
    ClientWins,
    /// <summary>
    /// Values stored database are preserved
    /// changes made by current user are discarded
    /// and entity is reloaded
    /// </summary>
    StoreWins
}
This one informs how the conflicts should be resolved. We can either overwrite all modified values with those the current user provided. This is called ClientWins strategy. We can also reload and take fresh copy of conflicted entity which is called StoreWins strategy. Finally we can also try to merge the conflicted entity in the way that it overwrites all properties that were changed by current user and were not modified by concurrency user. This is called Merge strategy obviously.

Next we need some helper objects which represent the result of save operation. Either there was no conflict or all conflicts were resolved or there is a conflict and save transaction was rollbacked. All of those information must be returned to the client code.

public class DbSaveResult
{
    /// <summary>
    /// Save code returned by raw SaveChanges method
    /// </summary>
    public int SaveCode { get; set; }
    /// <summary>
    /// The save failed and is not yet resolved
    /// </summary>
    public bool IsFailed { get; set; }
    /// <summary>
    /// Indicates that one or more netity have been reloded
    /// from database which means the view models and UI must be reloaded as well
    /// </summary>
    public bool ReloadRequired { get; set; }
}
 
/// <summary>
/// Represents a save result where the database concurrency conflict occured
/// </summary>
public class DbConflictResult : DbSaveResult
{
    /// <summary>
    /// Entity that causes last concurrency conflict
    /// </summary>
    public object Entity { get; set; }
    /// <summary>
    /// Entity values currently in database
    /// </summary>
    public Dictionary<string, object> DatabaseValues { get; set; }
    /// <summary>
    /// Indicates if entity was either deleted or modified by another user
    /// </summary>
    public DbConcurrenceConflict Conflict { get; set; }
 
    public DbConflictResult()
    {
        IsFailed = true;
        ReloadRequired = true;
    }
}

Then we have IDbConflictSolver interface.

public interface IDbConflictSolver
{
    DbConcurrenceStrategy Solve(DbConflictResult conflict);
}

In the solution we can either assume one concurrency resolution strategy for all kind of entities and for all save operations or we can ask user to react and decide about the strategy every time the changes are persisted. For the later case we need to implement above interface which should simply present the conflict result to the user interface in a comprehensible way and take the strategy user selects.

Next thing is to modify the SaveChanges method and override it in our entity context class.

public DbSaveResult SaveChanges(IDbConflictSolver solver = null, DbConcurrenceStrategy defaultStrategy = DbConcurrenceStrategy.ClientWins)
{
    DbSaveResult result = new DbSaveResult();
    var doSave = true;
    var reloadRequired = false;
    while (doSave)
    {
        try
        {
            result = new DbSaveResult { ReloadRequired = reloadRequired };
            result.SaveCode = base.SaveChanges();
            doSave = false;
        }
        catch (DbUpdateConcurrencyException ex)
        {
            result = ProcessConflicts(ex, out doSave, defaultStrategy, solver);
            reloadRequired |= result.ReloadRequired;
        }
    }
    return result;
}

We try to save changes and  call ProcessConflicts method when concurrency exception is thrown. Basically in every call to this method we handle only one entity conflict. This is because the EF will throw concurrency exception as soon as it detects single entity conflict, no aggregation of conflicts or something is made here.

Now lets look at the crucial part of the solution, the conflict processing method.

   1: private DbConflictResult ProcessConflicts(DbUpdateConcurrencyException saveException,
   2:           out bool resaveRequired,
   3:           DbConcurrenceStrategy defaultStrategy = DbConcurrenceStrategy.None,
   4:           IDbConflictSolver solver = null)
   5: {
   6:   if (saveException == null)
   7:       throw new ArgumentNullException("saveException");
   8:  
   9:   var entry = saveException.Entries.Single();
  10:   var databaseValues = entry.GetDatabaseValues();
  11:   resaveRequired = false;
  12:   var result = new DbConflictResult
  13:   {
  14:       Conflict = DbConcurrenceConflict.EntityModified,
  15:       Entity = entry.Entity
  16:   };
  17:  
  18:   DbConcurrenceStrategy resolveStrategy = defaultStrategy;
  19:  
  20:   if (databaseValues == null)
  21:   {
  22:       result.Conflict = DbConcurrenceConflict.EntityDeleted;
  23:       if (solver != null)
  24:           resolveStrategy = solver.Solve(result);
  25:   }
  26:   else
  27:   {
  28:       if (solver != null)
  29:       {
  30:           result.DatabaseValues = databaseValues.PropertyNames.ToDictionary(p => p, p => databaseValues[p]);
  31:           resolveStrategy = solver.Solve(result);
  32:           result.IsFailed = resolveStrategy != DbConcurrenceStrategy.None;
  33:       }
  34:  
  35:       if (resolveStrategy == DbConcurrenceStrategy.Merge)
  36:       {
  37:           var canMerge = true;
  38:           var mergedValues = databaseValues.Clone();
  39:           foreach (var p in entry.CurrentValues.PropertyNames)
  40:           {
  41:               if (entry.Property(p).IsModified)
  42:               {
  43:                   mergedValues[p] = entry.CurrentValues[p];
  44:  
  45:                   //value has been modified in both database and client and values are different
  46:                   if (!databaseValues[p].Equals(entry.OriginalValues[p]) &&
  47:                       !databaseValues[p].Equals(entry.CurrentValues[p]))
  48:                   {
  49:                       canMerge = false;
  50:                       break;
  51:                   }
  52:               }
  53:           }
  54:  
  55:           if (canMerge)
  56:           {
  57:               entry.OriginalValues.SetValues(databaseValues);
  58:               entry.CurrentValues.SetValues(mergedValues);
  59:               resaveRequired = true;
  60:           }
  61:       }
  62:       else if (resolveStrategy == DbConcurrenceStrategy.ClientWins)
  63:       {
  64:           //client wins
  65:           //overwrite original values with database values so
  66:           //ef won't detect confict changes and overwrites with current one
  67:           entry.OriginalValues.SetValues(databaseValues);
  68:           //repeat save to overwrite with current client values
  69:           resaveRequired = true;
  70:           result.ReloadRequired = false;
  71:       }
  72:       else if (resolveStrategy == DbConcurrenceStrategy.StoreWins)
  73:       {
  74:           //database wins
  75:           //reload values from database and discard current changes
  76:           entry.Reload();
  77:           //repeat save so other entities are stored
  78:           resaveRequired = true;
  79:       }
  80:  
  81:  
  82:       if (!resaveRequired && solver == null)
  83:           result.DatabaseValues = databaseValues.PropertyNames.ToDictionary(p => p, p => databaseValues[p]);
  84:  
  85:   }
  86:   return result;
  87: }

Here we can either set the default resolving strategy or provide a custom conflict solver. The database values are taken from the store and compared to current values of the conflicted entity. In lines 20-25 the case when the entity was deleted is handled. In line 28 the solver is used if provided and default resolving strategy is overwritten with one selected by user. Next we handles all three resolving strategies merge, client wins and store wins. I hope the rest is self explanatory.