在ASP .Net Singleton注入类中使用DbContext

时间:2016-03-31 11:33:47

标签: c# dependency-injection asp.net-core

我需要在我的Startup类中实例化的Singleton类中访问我的数据库。似乎直接注入它会导致处理掉的DbContext。

我收到以下错误:

  

无法访问已处置的对象。对象名称:' MyDbContext'。

我的问题有两个:为什么这不起作用?如何在单例类实例中访问我的数据库?

这是我的Startup类中的ConfigureServices方法:

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

这是我的控制器类:

public class TestController : Controller
{
    private FunClass _fun;

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

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

这是我的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;
    }
}

8 个答案:

答案 0 :(得分:17)

它不起作用的原因是因为.AddDbContext扩展名将每个请求添加为作用域。每个请求的范围通常是您想要的,通常每个请求都会调用一次保存更改,然后dbcontext将在请求结束时处理。

如果您确实需要在dbContext内使用singleton,那么您的FunClass类应该依赖于IServiceProviderDbContextOptions而不是直接依赖DbContext,这样你就可以自己创建它。

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

那就是说,我的建议是仔细考虑你是否真的需要你的FunClass成为一个单身人士,我会避免这种情况,除非你有充分的理由让它成为一个单身人士。

答案 1 :(得分:3)

解决方案是调用AddSingleton,我的类在我的Startup类的method参数中实例化:

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

解决方案是更改我的DbContext类:

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

}

然而,由于多人警告过,在单例类中使用DbContext可能是一个非常糟糕的主意。我的用法在实际代码中非常有限(不是示例FunClass),但我认为如果你这样做,最好找到其他方法。

答案 2 :(得分:3)

原始来源:https://entityframeworkcore.com/knowledge-base/51939451/how-to-use-a-database-context-in-a-singleton-service-

由于DbContext默认为作用域,因此您需要创建作用域来访问它。它还允许您正确处理其生命周期-否则,您将长时间保留DbContext实例,因此不建议这样做。

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.
        }
    }
}

答案 3 :(得分:3)

IServiceScopeFactory 可以做到这一点。

public class BattleManager : IBattleManager {

    private readonly IServiceScopeFactory scopeFactory;

    public BattleManager(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.
        }
    }
}

DbContext 的行为就像它在 using 语句中具有 Transient 范围一样。

答案 4 :(得分:3)

您可以在 DbContext 上使用此参数:

<块引用>

ServiceLifetime.Singleton

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

答案 5 :(得分:0)

无需重载MyDbContext的ctor。

services.AddSingleton(s=>new FunClass(new MyDbContext(new DbContextOptionsBuilder<MyDbContext>().UseSqlServer(configuration.GetConnectionString("DefaultConnection")).Options)));

答案 6 :(得分:0)

正如前面提到的.AddDbContext扩展将其添加为每个请求的作用域。因此,DI无法实例化Scoped对象来构造Singleton一个对象。

您必须自己创建和处置MyDbContext的实例,这更好,因为必须尽早使用DbContext来处置它。要传递连接字符串,您可以从Configuration类中获取Startup

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

Startup.cs中配置DbContextOptionBuilder并注册您的单身人士:

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

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

有点脏,但是效果很好。

答案 7 :(得分:-1)

asp.net核心的简单代码

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