web-dev-qa-db-ja.com

ASP .Net Singleton Injected ClassでDbContextを使用する

Startupクラスでインスタンス化されたSingletonクラスのデータベースにアクセスする必要があります。直接注入すると、DbContextが破棄されるようです。

次のエラーが表示されます。

破棄されたオブジェクトにアクセスできません。オブジェクト名:「MyDbContext」。

私の質問は2つあります。なぜこれが機能しないのか、シングルトンクラスインスタンスのデータベースにアクセスするにはどうすればよいのでしょうか。

次に、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;
    }
}
26
Tjaart

動作しない理由は、.AddDbContext拡張により、リクエストごとにスコープが設定されます。通常、リクエストごとにスコープが設定されます。通常、リクエストごとに変更を保存し、リクエストの最後にdbcontextを破棄します。

dbContext内でsingletonを本当に使用する必要がある場合、FunClassクラスはおそらくIServiceProviderおよびDbContextOptionsに依存する必要があります。 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を本当にシングルトンにする必要があるかどうかを慎重に検討することをお勧めします。シングルトンにする理由が特にない限り、それを避けます。

17
Joe Audette

更新

このソリューションがそれを行う正しい方法ではないことを完全に承知しています。何年も前にここでやったことをやらないでください。実際、シングルトンDbContextをまったく注入しないでください。

古い答え

解決策は、Startupクラスのmethodパラメーターでインスタンス化されたクラスでAddSingletonを呼び出すことでした。

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
Tjaart

前に述べたように.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 configure DbContextOptionBuilderとシングルトンを登録します。

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

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

少し汚れていますが、非常にうまく機能します。

1
Sergei Shvets

MyDbContextのctorをオーバーロードする必要はありません。

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