4

I am looking for the best way to handle concurrency while using Entity Framework. The simplest and most recommended (also on stack) solution is described here: http://msdn.microsoft.com/en-us/library/bb399228.aspx And it looks like:

try
{
    // Try to save changes, which may cause a conflict.
    int num = context.SaveChanges();
    Console.WriteLine("No conflicts. " +
    num.ToString() + " updates saved.");
}
catch (OptimisticConcurrencyException)
{
    // Resolve the concurrency conflict by refreshing the 
    // object context before re-saving changes. 
    context.Refresh(RefreshMode.ClientWins, orders);

    // Save changes.
    context.SaveChanges();
    Console.WriteLine("OptimisticConcurrencyException "
    + "handled and changes saved");
}

But is it enough? What if something changes between Refresh() and the second SaveChanges()? There will be uncaught OptimisticConcurrencyException?

EDIT 2:

I think this would be the final solution:

    int savesCounter = 100;
    Boolean saveSuccess = false;
    while (!saveSuccess && savesCounter > 0)
    {
        savesCounter--;
        try
        {
            // Try to save changes, which may cause a conflict.
            int num = context.SaveChanges();
            saveSuccess = true;
            Console.WriteLine("Save success. " + num.ToString() + " updates saved.");
        }
        catch (OptimisticConcurrencyException)
        {
            // Resolve the concurrency conflict by refreshing the 
            // object context before re-saving changes. 
            Console.WriteLine("OptimisticConcurrencyException, refreshing context.");
            context.Refresh(RefreshMode.ClientWins, orders);

        }
    }

I am not sure if Iunderstand how the Refresh() works. Does it refresh whole context? If yes, why does it take additional arguments (entities objects)? Or does it refreshes only objects specified? For example in this situation what should be passed as Refresh() second argument:

Order dbOrder = dbContext.Orders.Where(x => x.ID == orderID);
dbOrder.Name = "new name";
//here whole the code written above to save changes

should it be dbOrder?

rideronthestorm
  • 727
  • 1
  • 13
  • 32
  • 2
    +1 That was the same objection I had against this example when I first read it! Generally it is bad practice to do "risky" operations (saveChanges) in an exception handler. I was amazed to see this in official documentation. – Gert Arnold Mar 02 '12 at 14:05
  • 1
    Re: your change, it looks good. I being careful would probably put in a way to break the loop if say 100 retries fail to remedy the situation. Always hard to debug problems that end up in never ending loops. Even though they should never happen ;-) – Joachim Isaksson Mar 02 '12 at 14:42
  • 1
    Try the approach in this link it uses an different way of handling the concurreny , which might be more reliable apprach to follow:- http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/handling-concurrency-with-the-entity-framework-in-an-asp-net-mvc-application – John John Mar 16 '12 at 03:38

1 Answers1

4

Yes, even the second save may cause an OptimisticConcurrencyException if - as you say - something changes between Refresh() and SaveChanges().

The example given is just a very simple retry logic, if you need to retry more than once or resolve the conflict in a more complex way, you're better off creating a loop that will retry n times than nesting try/catch more than this single level.

Joachim Isaksson
  • 176,943
  • 25
  • 281
  • 294