1

I have recently stumbled across the fact that I cannot use the same instance of DBContext in separate threads. I have two services that share the same DBContext so I end up with an Exception

System.InvalidOperationException: 'A second operation started on this context before a previous operation completed. This is usually caused by different threads 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.'

So my code is mostly generated by ASP.Net core wizard

// program.cs

private static void ConfigureServices(string settings, IServiceCollection services)
{
  services.AddDbContext<MMCC.Terminal.Service.Data.TerminalDbContext>((provider, options) =>
{                options.UseNpgsql(configuration.GetConnectionString(nameof(TerminalDbContext)));
});
}

 public MyService1(TerminalDbContext dbTerminal)
        {
            _dbTerminal     = dbTerminal;
        }

 public MyService2(TerminalDbContext dbTerminal)
        {
            _dbTerminal     = dbTerminal;
        }

Usage

void Run()
{
    _hourTimer  = new Timer(UpdateOnceInHour, null, 0, 60 * 60 * 1000);
    _minuteTimer = new Timer(UpdateEveryMinute,  null, 0,  1 * 60 * 1000);
}

private async void UpdateOnceInHour(object state)
{
 _mService1.DoSomething()
}

private async void UpdateOnceInHour(object state)
{
 _mService2.DoSomething()
}

So here is when I have an exception.

    // Service 1 
// thread 1
    private DoSomething()
    {
      await _dbTerminal.SomeTable1.ToListAsync();
    }
    
    // Service 2
// thread 2
    private DoSomething()
    {
      await _dbTerminal.SomeTable2.ToListAsync();
    }

I am trying to figure out how to solve this problem. Is there any standard pattern? I tried refactoring my code, and instead of injecting TerminalDbContext into Service1, Service2 I do inject IServiceProvider, and then do GetService for TerminalDbContext but it seems that I end up with the same instance of DBContext.

Captain Comic
  • 15,744
  • 43
  • 110
  • 148
  • I have struggled with the same problem of using my DbContext all the time at the beginning. :-) – Tolbxela Oct 30 '20 at 13:06
  • 1
    You can specify the life time in [AddDbContext](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.entityframeworkservicecollectionextensions.adddbcontext), like `serrvices.AddDbContext(o => o.UseNpgsql(...), ServiceLifetime.Transient)`. – vernou Oct 30 '20 at 13:07
  • 1
    `Transient` : Specifies that a new instance of the service will be created every time it is requested. – vernou Oct 30 '20 at 13:09
  • 1
    I would be tempted to pass each service a brand new instance of dbContext when invoking `DoSomething()` this is probably closer to it's intended, short-lived, usage. – phuzi Oct 30 '20 at 13:10
  • 1
    @Vernou This comes with a whole bunch of implications regarding disposal and/or savechanges that are dealt with in a more cohesive fashion when using scoped lifetime. [This answer](https://stackoverflow.com/a/10588594/14357) (creating a factory) seems like best way forward. – spender Oct 30 '20 at 13:11

1 Answers1

1

You should be able use a using statement everywhere you need a unique instance and instantiate a new context that will be disposed when it is done.

using (var newContext = new TerminalDbContext(new DbContextOptionsBuilder<TerminalDbContext>()
{
    // Define options
}))
{
    DoSomething();
}