83

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;
    }
}
Tjaart
  • 3,912
  • 2
  • 37
  • 61
  • 5
    See [this answer](http://stackoverflow.com/questions/36246896/structuremap-creation-as-transient-per-request-not-working/36249145#36249145). 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. – NightOwl888 Mar 31 '16 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](http://stackoverflow.com/questions/10585478/one-dbcontext-per-web-request-why) provided by the creator of [Simple Injector](https://simpleinjector.org/index.html) that tries to explain why. I would strongly suggest to use a pattern like the *Repository* or *Unit of Work* patterns. – QuantumHive Mar 31 '16 at 12:22
  • @QuantumHive thanks. I have noted a warning in my working answer. – Tjaart Mar 31 '16 at 14:03
  • Possible duplicate of [Entity Framework Core service default lifetime](https://stackoverflow.com/questions/37507691/entity-framework-core-service-default-lifetime) – Michael Freidgeim Mar 18 '18 at 11:47
  • 3
    @QuantumHive - a DbContext IS a Unit of Work pattern. – Spivonious Feb 28 '20 at 21:34

5 Answers5

116

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.
        }
    }
}
Spook
  • 25,318
  • 18
  • 90
  • 167
  • 5
    This solution is better than the marked answer. It is easier to test. – NthDeveloper Apr 10 '20 at 14:55
  • 2
    Thanks! Nice solution. This takes a while to get your head around if you're coming from dependency injection in the Spring Framework. – phn Jul 17 '20 at 16:41
  • 3
    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(); – Mike Aug 13 '20 at 18:38
  • 3
    `keep instance of DbContext for a long time and this is not recommended.` - why? – Boppity Bop Aug 08 '21 at 09:53
  • "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). – Prisoner ZERO Sep 19 '22 at 00:19
41

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.

Mamun
  • 66,969
  • 9
  • 47
  • 59
Joe Audette
  • 35,330
  • 11
  • 106
  • 99
  • 10
    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( services => new FunClass(new GMBaseContext));`. If you need additional application services, you can resolve them within the factory method via `services.RequestService()` and pass it to the constructor of `GMBaseContext`. – Tseng Mar 31 '16 at 11:54
  • yes I agree it would be better to avoid it but I think you will find places where it is used in the framework itself. ie the constructor of DBContext, but now that I looked at the latest code, it seems that IServiceProvider is no longer needed in the constructor for DBContext in the latest code so that could be avoided, but I think as of RC1 it does need it in the constructor to get a DBContext that works – Joe Audette Mar 31 '16 at 12:11
  • I can't instantiate my DbContext with DbContextOptions because my context derives from IdentityDbContext, so there is no base constructor to pass DbContextOptions to. – Tjaart Mar 31 '16 at 12:11
  • 1
    in the latest identity/ef code [it does have a constructor that takes DBContextOptions](https://github.com/aspnet/Identity/blob/dev/src/Microsoft.AspNetCore.Identity.EntityFrameworkCore/IdentityDbContext.cs) I'm not sure about RC1 but would expect it to also have that – Joe Audette Mar 31 '16 at 12:18
  • 1
    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 – Joe Audette Mar 31 '16 at 12:19
  • I used @Tseng 's way of instantiating the Singleton, and overrode the OnConfiguring method, setting the connection string in the options on optionsbuilder. Your answer was definitely helpful but not the whole story. – Tjaart Mar 31 '16 at 12:25
  • And I will definitely be questioning my design that brought me to this point. The Singleton class runs a timer that checks a process on the host machine. I don't know if the web application is the right place for that to begin with. – Tjaart Mar 31 '16 at 12:26
  • 4
    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. – LHA Sep 10 '18 at 17:44
  • It doesn't work for me, because `DbContextOptions` is also `Scoped` and can not be resolved for singleton object – Sergei Shvets May 07 '19 at 10:21
  • 1
    @SergeiShvets at the time when I answered this DbContextOptions was injected as singleton by default, but [they changed that in EFCore2](https://github.com/aspnet/EntityFrameworkCore/pull/9009) so now it is registered as scoped by default but you can change it using an optional parameter in .AddDbContext(...) – Joe Audette May 08 '19 at 11:33
  • 1
    From the answer below - https://entityframeworkcore.com/knowledge-base/51939451/how-to-use-a-database-context-in-a-singleton-service- – shfire Feb 26 '20 at 11:31
12

You can use this parameter on your DbContext:

ServiceLifetime.Singleton

services.AddDbContext<EntityContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DatabaseConnection")), ServiceLifetime.Singleton);
alansiqueira27
  • 8,129
  • 15
  • 67
  • 111
  • 4
    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 – ihebiheb Jul 27 '21 at 18:56
  • 6
    using Singleton on DBcontext is terrible idea – Davidm176 Oct 25 '21 at 12:51
  • 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). – Jason Foglia Nov 18 '21 at 00:07
  • 3
    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. – Blue Apr 15 '22 at 23:05
6

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.

Community
  • 1
  • 1
Tjaart
  • 3,912
  • 2
  • 37
  • 61
  • Has anyone read this article? http://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. – Andez Jan 18 '18 at 22:29
  • Note that constructor `public MyContext(DbContextOptions options, string connectionString)' ignores options parameter. Is it OK? – Michael Freidgeim Feb 19 '18 at 03:14
  • When using a UnitOfWork pattern...you absolutely want a singleton DbContext. Otherwise, your "SubmitChanges" will not work across Repositories because each DbContext is different. – Prisoner ZERO Sep 11 '22 at 22:29
6

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.

Sergei Shvets
  • 1,676
  • 1
  • 14
  • 12
  • 1
    Seems from asp.net core 2.1, `DbContextOptions` is added as scoped which means even if you dispose `MyDbContext`, it still seems that a singleton can't depend on `DbContextOptions` either?. Is it still wiser to pass a single `DbContextOptions` as in your case here? – rethabile May 29 '20 at 07:27
  • `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 – Sergei Shvets May 29 '20 at 08:38
  • 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`. So that means `DbContextOptions` will never be injected per request as suggested by the framework. Hope I'm making a sense. – rethabile May 29 '20 at 08:57
  • As I understood from this pull request https://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`. – Sergei Shvets May 29 '20 at 09:12
  • Thanks for the link @Sergei I will peruse it once I get few seconds. – rethabile May 29 '20 at 13:17
  • `DbContext must be disposed after using as early as possible` - why? – Boppity Bop Aug 08 '21 at 09:54