Entity Framework Core service default lifetime

Rem picture Rem · May 29, 2016 · Viewed 27.6k times · Source

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?

Answer

Tseng picture Tseng · May 29, 2016

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();
    }
}