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

Tja*_*art 28 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>();
}
Run Code Online (Sandbox Code Playgroud)

这是我的控制器类:

public class TestController : Controller
{
    private FunClass _fun;

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

    public List<string> Index()
    {
        return _fun.GetUsers();
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

Spo*_*ook 64

原始来源: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.
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 该解决方案比标记的答案更好。测试起来更容易。 (3认同)
  • “长时间保留 DbContext 实例,不建议这样做。” - 为什么? (3认同)
  • 谢谢!很好的解决方案。如果您来自 Spring 框架中的依赖注入,那么这需要一段时间才能理解。 (2认同)
  • 除此之外,如果您使用从 DbContext 派生的类,请在调用中使用该类,否则您将收到“未注册类型‘Microsoft.EntityFrameworkCore.DbContext’的服务”。错误。因此调用如下所示: var db =scope.ServiceProvider.GetRequiredService&lt;MyDerivedDbContext&gt;(); (2认同)

Joe*_*tte 18

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

如果你真的需要在dbContext内部使用a 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 传递容器或`IServiceProvider`是一种反模式,因为它将您的类型绑定到特定容器(在这种情况下为`IServiceProvider`),应该避免使用.应该使用工厂方法或工厂类/接口来实现这一点.工厂方法可以实现像`services.AddSingleton <FunClass>(services => new FunClass(new GMBaseContext));`.如果您需要其他应用程序服务,可以通过`services.RequestService <SomeOtherDependency>()`在工厂方法中解析它们,并将其传递给`GMBaseContext`的构造函数. (3认同)
  • 这是一个糟糕的设计。DbContext不是线程安全的,因此在Singleton对象中具有DbContext实例将有问题。您通过了DI问题,但是并发失败。 (3认同)

Tja*_*art 6

更新

我完全意识到这个解决方案不是正确的方法。请不要做我多年前在这里所做的事情。事实上,根本不要注入单例 DbContext。

旧答案

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

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

解决方案是更改我的 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);
    }

}
Run Code Online (Sandbox Code Playgroud)

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


Ser*_*ets 5

如前所述,早期.AddDbContext扩展是将其添加为每个请求的范围。所以DI不能实例化Scoped对象来构造Singleton一个。

你必须MyDbContext自己创建和处理实例,这样更好,因为 DbContext 必须在使用后尽早处理。要传递连接字符串,您可以ConfigurationStartup类中获取:

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Startup.cs配置DbContextOptionBuilder和注册你的单身人士:

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

services.AddSingleton(new FunClass(optionsBuilder.Options));
Run Code Online (Sandbox Code Playgroud)

它有点脏,但效果很好。


ala*_*a27 5

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

ServiceLifetime.Singleton

services.AddDbContext<EntityContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DatabaseConnection")), ServiceLifetime.Singleton);
Run Code Online (Sandbox Code Playgroud)

  • 在 DBcontext 上使用 Singleton 是个糟糕的主意 (8认同)
  • 我认为将上下文设置为单例并不是一个好主意。因为交易时间较长,所以会有很多冲突 (4认同)
  • 不要这样做。不*真的*不要这样做。虽然这表面上看起来可行,但当多个调用进入控制器时,您将重用相同的数据上下文,这可能会导致多个线程尝试写入数据库和并发异常。 (3认同)