Use DbContext in ASP .Net Singleton Injected Class

后端 未结 5 646
刺人心
刺人心 2020-11-29 01:10

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 g

相关标签:
5条回答
  • 2020-11-29 01:50

    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.

    0 讨论(0)
  • 2020-11-29 01:53

    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.
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-29 01:56

    Simple code for asp.net core

      private readonly IConfiguration _configuration;
    
         public IdeasController( IConfiguration configuration)
          {
              _configuration = configuration;
          }
    
    var optionsBuilder = new DbContextOptionsBuilder<_YourContext>();         
     optionsBuilder.UseSqlServer(_configuration.GetConnectionString("DefaultConnection"));
    
       using (var context = new _YourContext(optionsBuilder.Options))          {
          var output = context.Users.Select(x=>x.Name).ToList();   }
    
    0 讨论(0)
  • 2020-11-29 01:59

    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.

    0 讨论(0)
  • 2020-11-29 02:05

    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.

    0 讨论(0)
提交回复
热议问题