1

I need to implement pessimistic concurrency control.

Basically, I want to wait for an action to finish before allowing it to be executed the second time because I want only 1 row with a specific value to exist at a time.

Example:

// I would like to block concurrent execution of this method (lock it until it's finished)
[HttpPost("open")]
public async Task<IActionResult> Open(ReportCashDrawerStateRequest request)
{
    ...

    // CONCURRENCY PROBLEM HERE: 
    // This check for uniqueness will pass if Open will be executed concurrently 
    // before SaveChangesAsync is called which will result in duplicate rows
    if (await _db.CashDrawerStates.AnyAsync(s => s.CashDrawerId == request.CashDrawerId && s.EndTime == null)) 
        return UnprocessableEntity("Already open");

    var cashDrawerState = new CashDrawerState
    {
        CashDrawerId = request.CashDrawerId,
        StartTime = DateTime.UtcNow,
        StartedById = User.GetUserId(),
        StartingCashAmount = request.CashAmount
    };

    // because check above will pass this will result in having 2 rows with EndTime==null 
    // which is unwanted.
    _db.CashDrawerStates.Add(cashDrawerState);
    await _db.SaveChangesAsync();

    ...
}

This is a business logic requirement, I think that adding a unique constraint (index) would solve this.

But is there any way to solve this by implementing some kind of lock in the Open method without adding a unique constraint on the database column?

I read https://learn.microsoft.com/en-us/ef/core/saving/concurrency but it only describes handling the conflict for updates and deletes and not for inserts.

Konrad
  • 6,385
  • 12
  • 53
  • 96
  • Have you tried using `TransactionScope`? https://weblogs.thinktecture.com/pawel/2018/06/entity-framework-core-use-transactionscope-with-caution.html – mjwills Feb 19 '19 at 12:17
  • @mjwills No, I haven't. Are you saying that this will prevent the conflict? (lock it until it's finished) – Konrad Feb 19 '19 at 12:20
  • 2
    @Konrad *why* do you want to use pessimistic concurrency? This hurts performance a *lot*. That's why it's not used since the late 90s. If anything, the problem is performing that uniqueness check. In SQL you could write `INSERT FROM WHERE` and ensure you only inserted a record if it didn't already exist. You can also return any inserted values with the `OUTPUT` clause. This single operation will be atomic – Panagiotis Kanavos Feb 19 '19 at 12:24
  • @Konrad this is one of the scenarios where ORM *can't work well*. It's far better to use a stored procedure or query and possibly map the results. – Panagiotis Kanavos Feb 19 '19 at 12:25
  • @PanagiotisKanavos I didn't know. I'm open to better solutions. It's important for me to inform the user that it's already open though. – Konrad Feb 19 '19 at 12:27
  • @PanagiotisKanavos as you say it would work if it was in 1 single query (`INSERT INTO WHERE`) or if I had the unique constraint set, then it would be a matter of handling the exception. – Konrad Feb 19 '19 at 12:49
  • Related: https://stackoverflow.com/questions/27005269/entity-framework-6-check-if-record-exists-before-insert-and-concurrency – Konrad Feb 19 '19 at 13:45

0 Answers0