Preventing duplicate key errors in EF-Core InMemory provider through locking
A lock works here, and it's functional, but it does come with some drawbacks in this case.

If you and I both submit the request at the same time, one of them will be delayed even though there wasn't actually a reason. Now you've introduced a potentially hazardous situation, what happens if a lot of distinct persons submit requests at once?

The best option here, is to block only those who violated the key sanity. The easiest way to do this is actually to catch the appropriate exception.

There's a nice question about this on Stack Overflow, which I would like to borrow some information from: Duplicate key exception from Entity Framework?


There is an answer that looks promising, which looks like:

try
{
    await db.SaveChangesAsync();
}
catch (DbUpdateException ex)
{
    SqlException innerException = ex.InnerException.InnerException as SqlException;
    if (innerException != null &amp;&amp; (innerException.Number == 2627 || innerException.Number == 2601))
    {
        //your handling stuff
    }
    else
    {
        throw;
    }
}


Based on the research I could suss-out in a short bit, this looks like it's appropriate for EF-Core, whereas the accepted answer looks more like EF on standard. I can't test it, so I leave it to you, but I would go more towards this route than a shared lock.

Of course, it's up to you, but I wouldn't even try to re-save the LastWrite on a duplicate key violation, because we know it's going to be right now, so why do the extra work? I'd just catch and swallow in this case, with a clear comment as to why:

try
{
    context.SaveChanges();
}
catch (DbUpdateException ex)
{
    SqlException innerException = ex.InnerException.InnerException as SqlException;
    if (innerException != null &amp;&amp; (innerException.Number == 2627 || innerException.Number == 2601))
    {
        // Swallow the exception, as the only thing we're updating is the `LastWrite`, which will be the current date/time so there's not point in doing additional queries;
    }
    else
    {
        throw;
    }
}


Depending on the logic, I may also even recommend tracking that further up the stack, and only call to this function if it needs updated. (Rather than for every update.)