98

I need to access my database in a Singleton class instantiated in my Startup class. It seems that injecting it directly results in a DbContext that is disposed.

I get the following error:

Cannot access a disposed object. Object name: 'MyDbContext'.

My question is twofold: Why doesn't this work and how can I access my database in a singleton class instance?

Here is my ConfigureServices method in my Startup class:

public void ConfigureServices(IServiceCollection services)
{
    // code removed for brevity

    services.AddEntityFramework().AddSqlServer().AddDbContext<MyDbContext>(
        options =>
        {
            var config = Configuration["Data:DefaultConnection:ConnectionString"];
            options.UseSqlServer(config);
        });

    // code removed for brevity

    services.AddSingleton<FunClass>();
}

Here is my controller class:

public class TestController : Controller
{
    private FunClass _fun;

    public TestController(FunClass fun)
    {
        _fun = fun;
    }

    public List<string> Index()
    {
        return _fun.GetUsers();
    }
}

Here is my FunClass:

public class FunClass
{
    private MyDbContext db;

    public FunClass(MyDbContext ctx) {
        db = ctx;
    }

    public List<string> GetUsers()
    {
         var lst = db.Users.Select(c=>c.UserName).ToList();
        return lst;
    }
}
5
  • 5
    See this answer. An object cannot have dependencies with a shorter lifetime than itself. You can either inject a factory to create shorter lived instances, or refactor so the root of the object graph is not a singleton. Commented Mar 31, 2016 at 12:02
  • 7
    I strongly discourage you to register your DbContext as a Singleton, there are many articles on the web that tell you why it's a bad idea. Here is an answer provided by the creator of Simple Injector that tries to explain why. I would strongly suggest to use a pattern like the Repository or Unit of Work patterns. Commented Mar 31, 2016 at 12:22
  • @QuantumHive thanks. I have noted a warning in my working answer. Commented Mar 31, 2016 at 14:03
  • Possible duplicate of Entity Framework Core service default lifetime Commented Mar 18, 2018 at 11:47
  • 3
    @QuantumHive - a DbContext IS a Unit of Work pattern. Commented Feb 28, 2020 at 21:34

5 Answers 5

132

Original source: https://entityframeworkcore.com/knowledge-base/51939451/how-to-use-a-database-context-in-a-singleton-service-

Since DbContext is scoped by default, you need to create scope to access it. It also allows you to handle its lifetime correctly - otherwise you'd keep instance of DbContext for a long time and this is not recommended.

public class Singleton : ISingleton 
{

    private readonly IServiceScopeFactory scopeFactory;

    public Singleton(IServiceScopeFactory scopeFactory)
    {
        this.scopeFactory = scopeFactory;
    }

    public void MyMethod() 
    {
        using(var scope = scopeFactory.CreateScope()) 
        {
            var db = scope.ServiceProvider.GetRequiredService<DbContext>();

            // when we exit the using block,
            // the IServiceScope will dispose itself 
            // and dispose all of the services that it resolved.
        }
    }
}
Sign up to request clarification or add additional context in comments.

5 Comments

This solution is better than the marked answer. It is easier to test.
Thanks! Nice solution. This takes a while to get your head around if you're coming from dependency injection in the Spring Framework.
Just to add to this, if you use a class that derives from DbContext, use that class in the call or else you'll get a "No service for type 'Microsoft.EntityFrameworkCore.DbContext' has been registered." error. So the call would look like: var db = scope.ServiceProvider.GetRequiredService<MyDerivedDbContext>();
keep instance of DbContext for a long time and this is not recommended. - why?
"RE: Keeping an instance of DbContext for a long time" In a web application, this isn't an issue because once the request is completed all objects are garbage collected. In a service bus or windows service, it seems natural to dispose of each set of objects as transactions are completed...so it isn't an issue there. In a WinForm application, you "might" keep some objects alive (the only issue is memory leaks & those can be managed).
40

The reason it doesn't work is because the .AddDbContext extension is adding it as scoped per request. Scoped per request is generally what you want and typically save changes would be called once per request and then the dbcontext would be disposed at the end of the request.

If you really need to use a dbContext inside a singleton, then your FunClass class should probably take a dependency on IServiceProvider and DbContextOptions instead of directly taking a dependency on the DbContext, that way you can create it yourself.

public class FunClass
{
    private GMBaseContext db;

    public FunClass(IServiceProvider services, DbContextOptions dbOptions) 
    {
        db = new GMBaseContext(services, dbOptions);
    }

    public List<string> GetUsers()
    {
         var lst = db.Users.Select(c=>c.UserName).ToList();
        return lst;
    }
}

That said, my advice would be to carefully consider whether you really need your FunClass to be a singleton, I would avoid that unless you have a very good reason for making it a singleton.

11 Comments

Passing the container or IServiceProvider is an anti-pattern as it binds your types to a specific container (IServiceProvider in this case) and should be avoided. one should use either factory method or factory class/interface to implement this. the factory method can be implemented liked like services.AddSingleton<FunClass>( services => new FunClass(new GMBaseContext));. If you need additional application services, you can resolve them within the factory method via services.RequestService<SomeOtherDependency>() and pass it to the constructor of GMBaseContext.
in the latest identity/ef code it does have a constructor that takes DBContextOptions I'm not sure about RC1 but would expect it to also have that
another way would be to use a more advanced DI like autofaq so you could registered a named instance for your singleton separate from the main one registered per request
This is a bad design. DbContext is not thread-safe so having DbContext instance in a Singleton object will have problem. You pass DI issue but you fail concurrency.
@SergeiShvets at the time when I answered this DbContextOptions was injected as singleton by default, but they changed that in EFCore2 so now it is registered as scoped by default but you can change it using an optional parameter in .AddDbContext(...)
|
17

You can use this parameter on your DbContext:

ServiceLifetime.Singleton

services.AddDbContext<EntityContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DatabaseConnection")), ServiceLifetime.Singleton);

8 Comments

I don't think that making the context a Singleton is a good idea. You will have many conflict as you will have a long transaction
using Singleton on DBcontext is terrible idea
Would it still be ok to use singleton if you have multiple context? I can think of a few scenarios like Features Flags or DB configured services (requirements).
Don't do this. No REALLY don't do this. While this may appear to work on the surface, when multiple calls come in to controllers, you'll reuse the same data context which can result in multiple threads trying to write to the database and concurrency exceptions.
@jdelano no it's not OK even for that. You could use SQLite for this scenario. Or EF's in-memory provider, which doesn't lose its contents when the DbContext is disposed
|
8

As mentioned early .AddDbContext extension is adding it as scoped per request. So DI can not instantiate Scoped object to construct Singleton one.

You must create and dispose instance of MyDbContext by yourself, it is even better because DbContext must be disposed after using as early as possible. To pass connection string you can take Configuration from Startup class:

public class FunClass
{
    private DbContextOptions<MyDbContext> _dbContextOptions;

    public FunClass(DbContextOptions<MyDbContext> dbContextOptions) {
        _dbContextOptions = dbContextOptions;
    }       

    public List<string> GetUsers()
    {
        using (var db = new MyDbContext(_dbContextOptions))
        {
            return db.Users.Select(c=>c.UserName).ToList();
        }
    }
}

In Startup.cs configure DbContextOptionBuilder and register your singleton:

var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
optionsBuilder.UseSqlServer(_configuration.GetConnectionString("DefaultConnection"));

services.AddSingleton(new FunClass(optionsBuilder.Options));

It's a little bit dirty, but works very well.

6 Comments

Seems from asp.net core 2.1, DbContextOptions<MyDbContext> is added as scoped which means even if you dispose MyDbContext, it still seems that a singleton can't depend on DbContextOptions<MyDbContext> either?. Is it still wiser to pass a single DbContextOptions<MyDbContext> as in your case here?
DbContextOptions is added as scoped to DI. So you can get instance of this class as dependency with per request lifetime. But there is not any link between objects are created by DI and created by yourself. Also as I know there is not any reason to stay live DbContextOption all the time
I don't think you are getting my question. the line services.AddSingleton(new FunClass(optionsBuilder.Options)); is adding a singleton of FunClass which is itself depending on DbContextOptions<MyDbContext>. So that means DbContextOptions<MyDbContext> will never be injected per request as suggested by the framework. Hope I'm making a sense.
As I understood from this pull request github.com/dotnet/efcore/pull/9009 - all the reasons are related to DI cases, and no reason related to EF. Another interesting point there that we can bring back singleton scope for DbContextOptions.
DbContext must be disposed after using as early as possible - why?
|
5

Update

I'm fully aware that this solution is not the right way to do it. Please don't do what I did here all those years ago. In fact, don't inject a singleton DbContext at all.

Old answer

The solution was to call AddSingleton with my class being instantiated in the method parameter in my Startup class:

services.AddSingleton(s => new FunClass(new MyContext(null, Configuration["Data:DefaultConnection:ConnectionString"])));

The solution was to change my DbContext class:

public class MyContext : IdentityDbContext<ApplicationUser>
{
    private string connectionString;

    public MyContext()
    {
        
    }

    public MyContext(DbContextOptions options, string connectionString)
    {
        this.connectionString = connectionString;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        // Used when instantiating db context outside IoC 
        if (connectionString != null)
        {
            var config = connectionString;
            optionsBuilder.UseSqlServer(config);
        }
     
        base.OnConfiguring(optionsBuilder);
    }

}

As multiple people have however warned, using a DbContext in a singleton class might be a very bad idea. My usage is very limited in the real code (not the example FunClass), but I think if you are doing this it would be better to find other ways.

3 Comments

Has anyone read this article? mehdi.me/ambient-dbcontext-in-ef6 I think it shows promise - for me it makes more sense for web apps to use a factory as opposed to a purely injected dbcontext - although possibly more for web applications with a lot of DI config where there maybe a performance gain is using singleton as opposed to per web request lifetimes.
Note that constructor `public MyContext(DbContextOptions options, string connectionString)' ignores options parameter. Is it OK?
When using a UnitOfWork pattern...you absolutely want a singleton DbContext. Otherwise, your "SubmitChanges" will not work across Repositories because each DbContext is different.

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.