关于c#:在ASP .Net Singleton注入类中使用DbContext

Use DbContext in ASP .Net Singleton Injected Class

我需要在一个在我的startup类中实例化的singleton类中访问我的数据库。似乎注入它会直接导致释放的dbContext。

我得到以下错误:

Cannot access a disposed object. Object name: 'MyDbContext'.

我的问题是双重的:为什么这不起作用,我如何在单例类实例中访问我的数据库?

以下是我的启动类中的configureServices方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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>();
}

这是我的控制器类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestController : Controller
{
    private FunClass _fun;

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

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

这是我的funclass:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
    }
}


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

如果你真的需要在singleton中使用dbcontext,那么你的FunClass类应该依赖IServiceProviderDbContextOptions而不是直接依赖dbcontext,这样你可以自己创建它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
    }
}

这就是说,我的建议是仔细考虑一下你是否真的需要你的娱乐班成为单身,除非你有一个很好的理由让它成为单身。


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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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并注册您的singleton:

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

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

有点脏,但效果很好。


不需要重载mydbContext的ctor。

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