Michał Fronczyk
Michał Fronczyk

Reputation: 1891

Create new DbContext dynamically when using ASP.Net Core dependency injection

I've configured my DbContext with services.AddDbContext() in the Startup class and constructor injection in my controllers works very well.

By default it's a scoped service, but I have one place in the app where I want to update a single property of an entity in a separate scope of work. So I need to create a new DbContext in the controller, but I'm not sure how. I want it to be created by the DI so I don't have to manually call the constructor and provide all the options needed. Is there a way to do that? Maybe there's a way to get the db context options from the DI? Then I could construct the DbContext easily.

Upvotes: 6

Views: 10247

Answers (2)

Matt
Matt

Reputation: 729

The normal method of injecting a DbContext into your Controller works fine, as long as you are doing a small amount of work during an HTTP request. However, you might want to create a DbContext for a long-running a operation that queries/modifies a lot of records (causing SaveChangesAsync() to get bogged down because DbContext.ChangeTracker is tracking a lot of objects). In that case, you can create a scoped DbContext for each operation ("unit of work"). Here is an example ASP.NET Core Controller method:

/// <summary>
/// An endpoint that processes a batch of records.
/// </summary>
/// <param name="serviceScopeFactory">The service scope factory to create scoped DbContexts.
/// This is injected by DI per the FromServices attribute.</param>
/// <param name="records">The batch of records.</param>
public async Task<IActionResult> PostRecords(
    [FromServices] IServiceScopeFactory serviceScopeFactory,
    Record[] records)
{
    foreach (var record in records)
    {
        // At the end of the using block, scope.Dispose() will be called,
        // releasing the DbContext so it can be disposed/reset.
        using (var scope = serviceScopeFactory.CreateScope())
        {
            var context = scope.ServiceProvider.GetService<MainDbContext>();

            // Query and modify database records as needed

            await context.SaveChangesAsync();
        }
    }

    return Ok();
}

Also, I would recommend switching from AddDbContext() to AddDbContextPool() in Startup.cs to avoid creating/destroying DbContext objects for each request. The DbContextPool will reset the DbContext objects to a clean state after they go out of scope. (In case you were interested, DbContextPool calls DbContext.ResetState() and DbContext.Resurrect(), but I wouldn't recommend calling those directly from your code, as they will probably change in future releases.) https://github.com/aspnet/EntityFrameworkCore/blob/v2.2.1/src/EFCore/Internal/DbContextPool.cs#L157

Finally, be aware that there are a few pitfalls of creating multiple DbContexts:

  • Using a large number of DbContexts in parallel may cause the database server to run out of active connections, since many EF database providers open a database connection per DbContext. (Requesting and releasing pooled DbContext objects in a loop should be fine.)
  • There may be more efficient ways to do the same thing. On my project, I tested and found that running a single "upsert" on a single DbContext was significantly faster than running a SELECT and INSERT/UPDATE on a separate DbContext for each record. There are a number of implementations of upsert for EF Core. For example, here are two that I have used:

Upvotes: 16

user3456014
user3456014

Reputation:

One option is to inject IDbContextFactory into your comtroller to create contexts within using blocks.

https://msdn.microsoft.com/en-us/library/hh506876(v=vs.113).aspx

Upvotes: 0

Related Questions