Reputation: 11
Working in a codebase using EF Core 5 on a Web API project with a Dependency Injected, Scoped EF Repository.
The code has worked well with Asynchronous queries (all await
ed), but I noticed the calls to SaveChanges()
were not async. A predecessor implemented SaveChanges()
via a Commit()
method in the base repository class (see below).
After switching the SaveChanges()
calls to SaveChangesAsync()
via CommitAsync()
in the base repository, even though these calls do await
their completion and no other threads should be using the Scoped repository (not spinning up any Tasks, etc.) and all preceding calls to the repository / DbContext
are being await
ed, we are still getting this exception:
A second operation was started on this context before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
I've spent hours on this now. We're not breaking any of the rules as per above. It makes no sense...
Here is our context-adding code in Startup - note, this is by default a request Scoped service:
services.AddDbContext<DbReadWriteContext>(
options => options.UseSqlServer(sqlReadWriteConnectionString,
providerOptions => providerOptions.UseNetTopologySuite().EnableRetryOnFailure(
maxRetryCount: 10,
maxRetryDelay: TimeSpan.FromSeconds(10),
errorNumbersToAdd: null
)));
Here is our Repository adding code in Startup - note these are also request Scoped:
services.AddScoped<ICompanyRepository, CompanyRepository>();
services.AddScoped<IAppointmentRepository, AppointmentRepository>();
Our Repositories inherit from a base repository which implements some 'helper' methods, including these two in question:
public IQueryable<T> GetQuery()
{
return _context.Set<T>();
}
public Task<T> GetSingleAsync(Expression<Func<T, bool>> predicate)
{
return _context.Set<T>().FirstOrDefaultAsync(predicate);
}
// Existing
public virtual int Commit()
{
return _context.SaveChanges();
}
// Newly added
public virtual Task<int> CommitAsync()
{
return _context.SaveChangesAsync();
}
And here is an example of code that fails with the above exception:
var appointment = await _appointmentRepository.GetQuery().Where(a => a.Id == appointmentId)
.Include(b => b.Business)
.FirstOrDefaultAsync();
var company = await _companyRepository.GetSingleAsync(c => c.Id == companyId);
if (company.SendNotice == true)
{
appointment.NoticeSent = true;
_appointmentRepository.Update(appointment);
await _appointmentRepository.CommitAsync();
}
There is simply no reason I see why we should be receiving this exception.
Upvotes: 0
Views: 256
Reputation: 11
For the benefit of others experiencing the same issue, we resolved this by replacing controller-wide Dependency Injection (DI) of Repositories/context with a DbContext within each controller endpoint, instantiated via the DI Service Provider.
Upvotes: 0