I have the following background service running:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Run(async () =>
{
while (!stoppingToken.IsCancellationRequested)
{
using (var scope = _scopeFactory.CreateScope())
{
DataContext dbContext = scope.ServiceProvider.GetRequiredService<DataContext>();
var tasks = new List<Task>();
tasks.Add(siteA.ProcessSchedule(SportLeagues.MLB, logger, dbContext));
tasks.Add(siteB.ProcessSchedule(SportLeagues.MLB, logger, dbContext));
//....more of same task
await Task.WhenAll(tasks);
await dbContext.SaveChangesAsync();
await Task.Delay(0, stoppingToken);
}
}
});
}
I constantly get this error
System.InvalidOperationException:
A second operation was started on this context instance before a previous operation completed.
I can refactor and create a dbContext
for each task, however I would prefer to use a single dbContext if possible.
I also tried adding my dbContext as transient:
builder.Services.AddDbContext<DataContext>(options =>
{
options.UseInMemoryDatabase("db");
}, ServiceLifetime.Transient);
But this did not help. Am I creating my dbContext in an incorrect way? Or is the best approach to create a new dbContext
for each task? For more context, this is a constantly running background task, it takes a second to complete and it will have around 20 tasks. It sounds pretty inefficient to be creating and destroying 20 dbContext
every second, along with having to do 20 dbContext
saves.
2
Answers
There are a couple of issues with your design. For one thing,
DbContext
is not thread-safe, and hence cannot be used from multiple tasks or threads safely.DbContext
is also tracking objects (unlessAsNoTracking()
and similar methods are used on all query operations). If you keep using the sameDbContext
instance for a long period of time, then it will take a lot of memory, and its performance might reduce as well.On the other hand, instantiating a new
DbContext
should be a cheap operation, unless it is bloated by containing too many tables – that would be a design flaw of a different kind.All these considerations point into direction of instantiating a new
DbContext
in every operation that needs it, rather than reusing instances. If subsequent operations need to communicate state, then reusing aDbContext
is one of the less favorable solutions to that end.Activating MARS in your Connection String should solve your problem.
like: "Data Source=MSSQL1;Initial Catalog=AdventureWorks;Integrated Security=SSPI;MultipleActiveResultSets=True"
Read This Microsoft Document For More Info:
https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/enabling-multiple-active-result-sets