3

Let's say I have an Entity which has a concurrency token column configured in EF core. When an exception occours because data the changed in the database, I'd like to retry the whole transaction from a clean context.

All of the examples use the following pattern:

using (var context = new PersonContext())
{
     try
     {
       //Business logic
     }
     catch (DbUpdateConcurrencyException ex)
     {
       //Reload/merge entries in ex.Entries, etc...
     }
}

The example is working, but how do you handle this scenario when the DbContext is registered as a scoped service, and it's injected into the repositories and you have a more complex scenario. I think it would be easier to retry the whole business process than handling the conflicts.

public class SomeService {
  public SomeService (ISomeRepository repo)
  {
  }

  public Task SomeMethod()
  {
     try
     {
       //Business logic
     }
     catch (DbUpdateConcurrencyException ex)
     {
       //clean context, restart SomeMethod()
     }
  }
}

What would be a clean way to handle the retry process?

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
kekefigure
  • 49
  • 3
  • It's not recommended to try to recover from a concurrency conflicts by automatically retrying, the recommended approach is just to rollback database and business transactions and let the end user decides if he want to retry it. – E-Bat Apr 29 '20 at 21:27
  • 3
    And what if it's irrelevant for the user? Like an invoce number which is generated from a sequential number? If two request running concurrently, one will fail, but an automatic rertry will solve it. – kekefigure May 02 '20 at 09:50
  • 1
    I have the same problem. Did you solve it? – julian zapata Aug 04 '20 at 18:30
  • 1
    Not really, but if you find a nice solution please share it. – kekefigure Aug 06 '20 at 17:24
  • I'm also trying to do this. I think the only way to do it is to inject `IServiceScopeFactory`, create a new scope and a new Context instance. It feels much more difficult than it should be. Being able to "Reset" a Context would be ideal – NickL Oct 24 '20 at 16:24

1 Answers1

0

You don't need to restart the whole DbContext. Just refresh the entity you are modifying.

You can replace the original database value of the entity with it's current value of the database like this:

public class SomeService {
  public SomeService (ISomeRepository repo)
  {
  }

  public Task SomeMethod(Dto dto)
  {
     try
     {
       //Business logic
     }
     catch (DbUpdateConcurrencyException ex)
     {
        var entry = e.Entries.FirstOrDefault(x => x.Entity is <your entity type> ); // You can also check the Id.
        if(null == entry)
            throw e;
                
        var dbValue = entry.GetDatabaseValues();
            
        if (null == dbValue) 
            throw new Exception("The entity is deleted from the database in the meanwhile.");

        entry.OriginalValues.SetValues(dbValue);
        SomeMethod(dto);
     }
  }
}

EF checks the optimistic concurrency token with the original database values.

Mohsen
  • 4,000
  • 8
  • 42
  • 73