Ich muss auf meine Datenbank in einer Singleton-Klasse zugreifen, die in meiner Startup-Klasse instanziiert wurde. Es scheint, dass das Injizieren direkt zu einem DbContext führt, der entsorgt wird.
Ich erhalte folgende Fehlermeldung:
Kann nicht auf ein entsorgtes Objekt zugreifen. Objektname: 'MyDbContext'.
Meine Frage ist zweifach: Warum funktioniert das nicht und wie kann ich in einer Einzelklasseninstanz auf meine Datenbank zugreifen?
Hier ist meine ConfigureServices-Methode in meiner Startup-Klasse:
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>();
}
Hier ist meine Controller-Klasse:
public class TestController : Controller
{
private FunClass _fun;
public TestController(FunClass fun)
{
_fun = fun;
}
public List<string> Index()
{
return _fun.GetUsers();
}
}
Hier ist meine 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;
}
}
Der Grund, warum es nicht funktioniert, ist, dass die Erweiterung .AddDbContext
es als Bereich pro Anforderung hinzufügt. Der Umfang pro Anforderung ist in der Regel das, was Sie möchten, und Änderungen werden normalerweise einmal pro Anforderung aufgerufen. Anschließend wird dbcontext
am Ende der Anforderung verworfen.
Wenn Sie wirklich eine dbContext
in einer singleton
verwenden müssen, sollte Ihre FunClass
-Klasse wahrscheinlich eine Abhängigkeit von IServiceProvider
und DbContextOptions
haben, anstatt direkt von der DbContext
-Komponente abhängig zu sein.
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;
}
}
Das heißt, mein Rat wäre es, sorgfältig zu überlegen, ob Sie Ihre FunClass wirklich als Singleton benötigen. Das würde ich vermeiden, es sei denn, Sie haben einen sehr guten Grund, es als Singleton zu verwenden.
Die Lösung bestand darin, AddSingleton aufzurufen, wobei meine Klasse im Methodenparameter in meiner Startup-Klasse instanziiert wurde:
services.AddSingleton(s => new FunClass(new MyContext(null, Configuration["Data:DefaultConnection:ConnectionString"])));
Die Lösung bestand darin, meine DbContext-Klasse zu ändern:
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);
}
}
Da jedoch mehrere Personen gewarnt haben, kann die Verwendung eines DbContext in einer Singleton-Klasse eine sehr schlechte Idee sein. Meine Verwendung ist im echten Code sehr begrenzt (nicht im Beispiel FunClass), aber ich denke, wenn Sie dies tun, wäre es besser, andere Wege zu finden.
Wie bereits erwähnt, wird die Erweiterung .AddDbContext
Je nach Anforderung hinzugefügt. DI kann also kein Scoped
-Objekt instanziieren, um Singleton
eines zu konstruieren.
Sie müssen die Instanz von MyDbContext
selbst erstellen und entsorgen. Dies ist sogar noch besser, da DbContext nach der Verwendung so früh wie möglich entsorgt werden muss. Um eine Verbindungszeichenfolge zu übergeben, können Sie Configuration
aus der Klasse Startup
nehmen:
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();
}
}
}
Konfigurieren Sie in Startup.cs
DbContextOptionBuilder
und registrieren Sie Ihren Singleton:
var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
optionsBuilder.UseSqlServer(_configuration.GetConnectionString("DefaultConnection"));
services.AddSingleton(new FunClass(optionsBuilder.Options));
Es ist ein bisschen schmutzig, funktioniert aber sehr gut.
Ctor von MyDbContext muss nicht überladen werden.
services.AddSingleton(s=>new FunClass(new MyDbContext(new DbContextOptionsBuilder<MyDbContext>().UseSqlServer(configuration.GetConnectionString("DefaultConnection")).Options)));