Concurrency Exceptions - Entity Framework 6 and EF Core

Overview

This article outlines the steps to handle concurrency exceptions in Entity Framework 6.

Concurrency exceptions happen only on entities/tables that implement a row version field. They occur when EF detects that an underlying data record has changed since it was first retrieved. This frequently happens because two processes or users are trying to edit the record simultaneously. (Note that a concurrency exception is only relevant for updates — not inserts or deletes.)

References


Implementation

Notes


Data Model

The entity/table that we want to implement optimistic concurrency on must have a field similar to the following.

[Timestamp]
public byte[] RowVersion { get; set; }

Exception Handling and Retry Logic

var done = false;
var iterNum = 1;
var maxIterations = 5;

do
{
    try
    {

        LoggingEngine.Info($"Starting Iteration {iterNum}.");

        /*  TODO: Implement logic to update record. */

        db.SaveChanges();

        done = true;
    }
    catch (SqlException ex) when (ex.Number == 1205) // 1205 = deadlock victim
    {
        iterNum++;
        done = (iterNum >= maxIterations);
        if (done)
        {
            LoggingEngine.Exception($"Deadlock exception while trying to "
                + $"do stuff - iteration {iterNum}. EXITING!", ex);
        }
        else
        {
            LoggingEngine.Exception($"Deadlock exception while trying to "
                + $"do stuff - iteration {iterNum}. Retrying operation.", ex);
        }
    }
    catch (DbUpdateConcurrencyException ex)
    {
        iterNum++;
        done = (iterNum > maxIterations);

        if (done)
        {
            LoggingEngine.Exception($"Concurrency exception while trying to " 
                + $"do stuff - iteration {iterNum-1}. EXITING!", ex);
        }
        else
        {
            LoggingEngine.Exception($"Concurrency exception while trying to "
                + $"do stuff - iteration {iterNum-1}. Retrying.", ex);

            foreach (var en in ex.Entries)
            {
                en.Reload(); // RELOAD = database wins
            }
        }
    }
    catch (Exception ex)
    {
        done = true;
        LoggingEngine.Exception($"Exception while trying to do stuff", ex);
    }
} while (!done);

Useful Functions

The following functions are useful to convert the [RowVersion] field above to/from a 64-bit integer. This can easily be passed to/from the front end code for the use case where the user needs to resolve the conflict. (Note: This is written in C#.NET Core.)

private long ByteArrayToLong(byte[] input)
{
    if (input.Length > 8)
    {
        throw new InvalidOperationException("Byte array can have no more than 8 elements.");
    }

    var result = BitConverter.ToInt64(input.Reverse().ToArray(), 0);

    return result;
}
private byte[] LongToByteArray(long input)
{
    var result = BitConverter.GetBytes(input).Reverse().ToArray();
    return result;
}