skip to Main Content

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


  1. 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 (unless AsNoTracking() and similar methods are used on all query operations). If you keep using the same DbContext 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 a DbContext is one of the less favorable solutions to that end.

    Login or Signup to reply.
  2. 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

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search