I have a given entity ‘Student’ with application based concurrency control defined on ‘LastModified’.
public class Student
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public DateTimeOffset Created { get; set; }
[ConcurrencyCheck]
public DateTimeOffset LastModified { get; set; }
}
Now, I have update function written in repository layer as below:
public async Task<UpsertResponse> UpdateAsync(Entities.Student student)
{
using var context = _dbContextFactory.CreateDbContext(readOnly: true); //Create reader DbContext instance
var studentToBeModified = await context.Student.FirstOrDefaultAsync(x => x.Id == student.Id).ConfigureAwait(false);
if (!studentToBeModified)
{
studentToBeModified.Name = student.Name;
studentToBeModified.LastModified = DateTimeOffset.UtcNow;
using var context = _dbContextFactory.CreateDbContext(readOnly: false); //Create writer DbContext instance
context.Update(studentToBeModified);
await context.SaveChangesAsync().ConfigureAwait(false); //Throws DbUpdateConcurrencyException
}
}
Above problem can be fixed in below 2 ways with some Pros/Cons:
- Use one writer
DbContext
instance and use it for both read and write operation.
Cons: Not a best way to distribute load on read and write db instance, as write instance is only 1 but read instances can scale to many - Write
SaveChangesAsync
interceptor to take care to update LastModified value.
Cons: Overhead to have an interceptor
What the better way to handle application based concurrency check using Entity Framework without impacting application performance?
Kindly share if another option is also available
2
Answers
SaveChangesAsync()
will suceeed if, before modifying properties ofstudentToBeModified
, you useAttach()
to start tracking it in the writercontext
.The body of your
if
would become:To handle Optimistic Concurrency you can’t use a DateTime property generated by your application, because you will surely understand that in a multi-user environment there could be another user generating the same value for the same property, in the same instant.
If you are connecting to SQL Server, a safer choice could be to add this property in each of your entity classes:
as Microsoft also recommends in its documentation. You should enclose your saving operations inside a "try…catch" block, and repeat it (ie. with a "while") if an Update Concurrency Exception occurs:
Also, in your code, it’s convenient to use one DbContext only, for reading and writing, so studentToBeModified will be attached to the context when read, and you can modify and save it, in just a few steps. Entity Framework operations are an Unit of Work and must be concluded in a short time. So using one YourContext only, ContextFactory wouldn’t even be necessary. Anyway, I modified your code a bit: