1
votes

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.

2
You can get the DbContextOptions from DI and then create a new DbContext with it. - Brad
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? - Roelant M
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. - Michał Fronczyk

2 Answers

7
votes

The normal method of injecting a DbContext into your Controller works fine, as long as you are doing a reasonable amount of work during an HTTP request. However, you might want to create a DbContext if you are running a background/parallel operation, or if you are querying and modifying a lot of records (causing SaveChangesAsync() to get bogged down because DbContext.ChangeTracker is tracking a lot of objects). If so, you can create a scoped DbContext for each operation. Here is an example ASP.NET Core Controller method:

    /// <summary>
    /// An endpoint that processes a batch of records.
    /// </summary>
    /// <param name="provider">The service provider 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] IServiceProvider provider,
        Record[] records)
    {
        // The service scope factory is used to create a scope per iteration
        var serviceScopeFactory = 
            provider.GetRequiredService<IServiceScopeFactory>();

        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

0
votes

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