Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handle Entity Framework OptimisticConcurrencyException

Evaluating the .NET Entity Framework I try to find the right patterns to handle concurrent updates with optimistic concurrency mode.

In the documentation and many other places I see the following pattern:


Try
  ' Try to save changes, which may cause a conflict. 
  Dim num As Integer = context.SaveChanges()
  Console.WriteLine("No conflicts. " & num.ToString() & " updates saved.")
Catch generatedExceptionName As 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")
End Try

I see the following problems with this

  • it automatically implements last-in-wins instead of using optimistic mode
  • it is not robust: concurrent changes between .Refresh and .SaveChanges can cause a new OptimisticConcurrencyException

Is this correct, or am I missing something?

In a UI I normally let the user resolve the concurrency conflict:


Try
   _ctx.SaveChanges()
Catch ex As OptimisticConcurrencyException
   MessageBox.Show("Data was modified by another User." & vbCrLf &
   "Click 'Refresh' to show the current values and reapply your changes.",
   "Concurrency Violation", MessageBoxButton.OK)
End Try

In business logic I normally use a retry loop around the whole business transaction (reading and updating):


Const maxRetries = 5, retryDelayMs = 500
For i = 1 To maxRetries
    Try
        Using ctx As New EFConcurrencyTest.ConcurrencyTestEntities
            ctx.Inventories.First.QuantityInStock += 1
            System.Threading.Thread.Sleep(3000) 'Cause conflict
            ctx.SaveChanges()
        End Using
        Exit For
    Catch ex As OptimisticConcurrencyException
        If i = maxRetries Then Throw
        System.Threading.Thread.Sleep(retryDelayMs)
    End Try
Next

With EF I plan to encapsulate the loop:


ExecuteOptimisticSubmitChanges(Of EFConcurrencyTest.ConcurrencyTestEntities)(
    Sub(ctx)
        ctx.Inventories.First.QuantityInStock += 1
        System.Threading.Thread.Sleep(3000) 'Cause conflict
    End Sub)

See:

Functional Optimistic Concurrency in C#

Retryable actions in C#

like image 748
Peter Meinl Avatar asked Sep 16 '10 10:09

Peter Meinl


1 Answers

This:

Catch ex As 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")

...is completely pointless. If the only thing you do when you "handle" the exception is to ignore it and save anyway, you should just turn optimistic concurrency off; you're writing code to work around an optional feature.

So, yes, I'd say the documentation is not giving you good advice here.

Your proposed UI code is a better solution.

like image 134
Craig Stuntz Avatar answered Sep 17 '22 02:09

Craig Stuntz