Jasinski Technical Wiki

Navigation

Home Page
Index
All Pages

Quick Search
»
Advanced Search »

Contributor Links

Create a new Page
Administration
File Management
Login/Logout
Your Profile

Other Wiki Sections

Software

PoweredBy

Concurrency Exceptions - Entity Framework 6 and EF Core

RSS
Modified on Mon, Mar 29, 2021, 4:28 PM by Administrator Categorized as EF Core, Entity Framework Code First, ·Net Framework

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

  • The following code works equally well in EF6 as EF Core.
  • The RowVersion field that is added is nullable.

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

ScrewTurn Wiki version 3.0.1.400. Some of the icons created by FamFamFam. Except where noted, all contents Copyright © 1999-2024, Patrick Jasinski.