73

In ASP.NET Core application I can register DbContext through DI like this

services.AddDbContext<Models.ShellDbContext>(options => options.UseNpgsql(connection));

And it is intersting to know what is its lifetime?

From here https://github.com/aspnet/EntityFramework/blob/f33b76c0a070d08a191d67c09650f52c26e34052/src/Microsoft.EntityFrameworkCore/EntityFrameworkServiceCollectionExtensions.cs#L140 it looks like it is configured as Scoped that means DbContext instance is created on every request.

So first part of the question is: Is it true and if yes, then how costly it is?

And second part is: If I create a service what consumes DbContext, and intended to be consumed by Controllers, and will have an API to manage some entities in DB, should it be registered as Scoped also?

Tseng
  • 61,549
  • 15
  • 193
  • 205
Roman Kolesnikov
  • 11,777
  • 11
  • 44
  • 67

3 Answers3

95

Yes, the default life time for DbContext is scoped. This is intended this way.

Instantiating DbContext is pretty cheap and it makes sure that the your do not use to many resources. If you'd have a DbContext with a singleton lifetime, then all records that you read once will be tracked by the DbContext, unless you specifically disable tracking. This will require much more memory usage and it will keep growing.

And the more the DbContext tracks, the lower the performance will be. That's why you often see DbContext only used within a using(var context = new AppDbContext()) block.

In web applications however, using the using block is bad, because the lifetime is managed by the framework and if you dispose it to early the calls after that will fail with an exception.

If you use transient lifetime on the other side, you will lose the "transaction" functionality. With scoped, the DbContext has a transaction scope that's as long as the request.

If you need more fine-grained control, you have to use the Unit of Work pattern (which DbContext already kind of utilize).

For your second question:

If you create a service, it must have a lifetime that's equal to the one of the scope or shorter (read: Scoped or transient).

If you explicitly need a longer life-time for a service, you should inject a DbContext factory service or factory method into your service.

You can accomplish this with something like

services.AddTransient<Func<AppDbContext>>( (provider) => new Func<MyDbContext>( () => new AppDbContext()));
services.AddSingleton<IMySingletonService, MySingletonService>();

And your service may look like this:

public class MySingletonService : IMySingletonService, IDisposable
{
    private readonly AppDbContext context;

    public MySingletonService(Func<AppDbContext> contextFactory)
    {
        if(contextFactory == null)
            throw new ArgumentNullException(nameof(contextFactory));

        // it creates an transient factory, make sure to dispose it in `Dispose()` method.
        // Since it's member of the MySingletonService, it's lifetime
        // is effectively bound to it. 
        context = contextFactory();
    }
}
Anthony Johnston
  • 9,405
  • 4
  • 46
  • 57
Tseng
  • 61,549
  • 15
  • 193
  • 205
  • 2
    in a web app is it really the controller which manages lifetime? or is the the DI which disposes of things scoped per request at the end of the request? I would like to understand that better – Joe Audette May 29 '16 at 15:28
  • My bad. I meant to say framework. There is a controller factory (`IControllerFactory`, see https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerFactory.cs for default implementation) in it's `CreateController`method that creates the controller and dispose it and dispose it and it's dependencies in `ReleaseController()` method. Also see this GitHub issue on more details of the inner workings and why it works that way: https://github.com/aspnet/Mvc/issues/3727 – Tseng May 29 '16 at 16:31
  • thanks. if I have a repository that takes a DbContext in its constructor and it implements IDisposable is it supposed to call dispose on the DbContext from its own dispose method? I notice the EF UserStore for Identity does not call dispose on the dbcontext but it feels wrong for me not to so I'm trying to understand why they did not – Joe Audette May 30 '16 at 11:09
  • If you directly inject `DbContext` then no, don't dispose it. Notice that in my answer I pass an delegate `Func contextFactory` where the delegates creates a new instance of the factory on each call to it – Tseng May 30 '16 at 13:54
  • My 5 cents about tracking on select. If you don't wanna track collections of your entities then you can just use AsNoTracking() method. I'm not 100% sure but in theory it should save resources and reduce query time a bit. – Alex Gurskiy Jul 18 '18 at 01:02
  • What's written in IMySingletonService? – Pedro Ávila Jun 29 '20 at 20:08
  • @PedroÁvila: Doesn't matter, arbitrary interface for a service with public method that specific service needs. Usually one always injects interfaces so unit tests become easier to replace with mocks – Tseng Jun 30 '20 at 16:54
  • Now it is probably best to use `services.AddDbContextFactory()` to create new context instances. After adding this service, you can inject both `IDbContextFactory` and `AppDbContext` like normal. – Macadameane Mar 30 '23 at 20:53
18

Note: In EF Core 2 there is now a new method AddDbContextPool which creates a pool of contexts that can be reused. The scoping is still the same, but the instance will be 'reset' and returned to the pool. I would have thought the overhead of 'resetting' would be the same as just creating a new one but I guess that isn't the case.

If this method is used, at the time a DbContext instance is requested by a controller we will first check if there is an instance available in the pool. Once the request processing finalizes, any state on the instance is reset and the instance is itself returned to the pool.+

This is conceptually similar to how connection pooling operates in ADO.NET providers and has the advantage of saving some of the cost of initialization of DbContext instance.

https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-2.0#high-performance

Edward Brey
  • 40,302
  • 20
  • 199
  • 253
Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
-6

Admittedly anecdotal, this is what I’ve learned. And it’s from a lazy KISS devotion. I avoided other complications and solved my issue of premature EF Core DbContext disposes w/o concern of pools, scopes etc. I was merely throwing together a POC w/o regard to async return values, chained up from the Controller to a Repository and so on, which all essentially returned “void”, i.e. literally the singular, “Task”. That caused iterations amongst my DbContext members in lower routines to inexplicably dispose of the underlying DbContext. All I had to do was make every async method return a Task<whatever return> value and everything worked. EF Core don’t like async void return values.

KramFfud
  • 179
  • 1
  • 2
  • 6