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;
}
}
動作しない理由は、.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を本当にシングルトンにする必要があるかどうかを慎重に検討することをお勧めします。シングルトンにする理由が特にない限り、それを避けます。
このソリューションがそれを行う正しい方法ではないことを完全に承知しています。何年も前にここでやったことをやらないでください。実際、シングルトン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ではありません)が、これを行う場合は他の方法を見つけた方が良いと思います。
前に述べたように.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));
少し汚れていますが、非常にうまく機能します。
MyDbContextのctorをオーバーロードする必要はありません。
services.AddSingleton(s=>new FunClass(new MyDbContext(new DbContextOptionsBuilder<MyDbContext>().UseSqlServer(configuration.GetConnectionString("DefaultConnection")).Options)));