6

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.

3
  • 1
    You can get the DbContextOptions from DI and then create a new DbContext with it. Commented Apr 12, 2018 at 23:03
  • If it is in your controller, then why not DI? Why not creating a service that abstracts that away from your controller and you let take Di take care of the rest? Commented Apr 13, 2018 at 6:58
  • I want to use DI, but the DbContext is configured as a scoped service, so the DI always returns me the same instance in the context of a single request. But I want a new one and I'm not sure how to ask DI for a new instance. Getting DbContextOptions from DI sounds promising. I'm also thinking about create a new DI scope and then ask it for a new DbContext instance. Commented Apr 17, 2018 at 12:44

2 Answers 2

17

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:
Sign up to request clarification or add additional context in comments.

2 Comments

Great Answer, I wasn't familiar with the IServiceScopeFactory so the code example was very helpful. I was looking to do a similar thing with .NET Core v1 and EF 6. For future folks, by in large the code was the same.
Needed this in an API endpoint that sometimes calls itself, pass itself the initial provider and replace the normal db instance with the scoped context. Very good answer! Microsoft expects the reader to understand context pattern here, the only official reference to the error I was getting: learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-6.0/…
0

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

3 Comments

IDbContextFactory is marked as obsolete in .net core 2.0.
Interesting - so is there a documentation link to that, because the link I posted doesn't mention this.
The link you posted is not about EF core. I can't find an API documentation, but it's mentioned here for example - learn.microsoft.com/en-us/ef/core/miscellaneous/1x-2x-upgrade

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.