Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EF core DbContext in a multithreaded API application

tl;dr How can I use Entity Framework in a multithreaded .NET Core API application even though DbContext is not threadsafe?

Context

I am working on a .NET Core API app exposing several RESTful interfaces that access the database and read data from it, while at the same time running several TimedHostedServices as background working threads that poll data regularly from other webservices and store them into the database.

I am aware of the fact that DbContext is not threadsafe. I read a lot of docs, blog Posts and answers here on Stackoverflow, and I could find a lot of (partly contradictory) answers for this but no real "best practice" when also working with DI.

Things I tried

Using the default ServiceLifetime.Scoped via the AddDbContext extension method results in exceptions due to race conditions.

I don't want to work with locks (e.g. Semaphore), as the obvious downsides are:

  • the code is polluted with locks and try/catch/finally for safely releasing the locks
  • it doesn't really seem 'robust', i.e. when I forget to lock a region that accesses the DbContext.
  • it seems redundant and 'unnatural' to artificially syncronize db access in the app when working with a database that also handles concurrent connections and access

Not injecting MyDbContext but DbContextOptions<MyDbContext> instead, building the context only when I need to access the db, using a using statement to immediatelly dispose it after the read/write seems like a lot of resource usage overhead and unnecessarily many connection opening/closings.

Question

I am really puzzled: how can this be achived?

I don't think my usecase is super special - populating the db from a Background worker and querying it from the web API layer - so there should be a meaningful way of doing this with ef core.

Thanks a lot!

like image 970
Philip Daubmeier Avatar asked Apr 20 '19 19:04

Philip Daubmeier


2 Answers

You should create a scope whenever your TimedHostedServices triggers.

Inject the service provider in your constructor:

public MyServiceService(IServiceProvider services)
{
    _services = services;
}

and then create a scope whenever the task triggers

using (var scope = _services.CreateScope())
{
    var anotherService = scope.ServiceProvider.GetRequiredService<AnotherService>();

    anotherService.Something();
}

A more complete example is available in the doc

like image 54
ESG Avatar answered Oct 24 '22 18:10

ESG


Another approach to create own DbContextFactory and instantiate new instance for every query.

public class DbContextFactory
{
    public YourDbContext Create()
    {
        var options = new DbContextOptionsBuilder<YourDbContext>()
            .UseSqlServer(_connectionString)
            .Options;

        return new YourDbContext(options);
    }
}

Usage

public class Service
{
    private readonly DbContextFactory _dbContextFactory;

    public Service(DbContextFactory dbContextFactory) 
         => _dbContextFactory = dbContextFactory;

    public void Execute()
    {
        using (var context = _dbContextFactory.Create())
        {
            // use context
        }
    }
}    

With factory you don't need to worry about scopes anymore, and make your code free of ASP.NET Core dependencies.
You will be able to execute queries asynchronously, which not possible with scoped DbContext without workarounds.
You always be confident about what data saved when calling .SaveChanges(), where with scoped DbContext there are possibilities that some entity were changed in other class.

like image 35
Fabio Avatar answered Oct 24 '22 16:10

Fabio