在 ASP.Net 单例注入类中使用 DbContext

我需要访问在我的启动类中实例化的 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();
}
}

这是我的趣味班:

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;
}
}
80293 次浏览

它无法工作的原因是因为 .AddDbContext扩展根据每个请求的作用域添加了它。每个请求的作用域通常是您想要的,通常每个请求调用一次保存更改,然后在请求结束时释放 dbcontext

如果您确实需要在 singleton中使用 dbContext,那么您的 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 是一个单例模式,我会避免这种情况,除非你有一个非常好的理由使它成为一个单例模式。

更新

我完全明白这个解决方案不是正确的方法。请不要重蹈我多年前的覆辙。事实上,根本不要注入单一的 DbContext。

旧答案

解决方案是调用 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) ,但我认为如果您正在这样做,最好找到其他方法。

正如前面提到的,.AddDbContext扩展根据每个请求的作用域添加它。所以 DI 不能实例化 Scoped对象来构造 Singleton对象。

您必须自己创建和释放 MyDbContext的实例,这样更好,因为在尽早使用之后必须释放 DbContext。要传递连接字符串,可以从 Startup类获取 Configuration:

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

有点脏,但效果很好。

来源: 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.
}
}
}

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

终身服务,辛格尔顿

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