wake-up-neo.com

Verwenden Sie DbContext in ASP .Net Singleton Injected Class

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;
    }
}
18
Tjaart

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.

14
Joe Audette

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.

3
Tjaart

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.csDbContextOptionBuilder 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.

1
Sergei Shvets

Ctor von MyDbContext muss nicht überladen werden. 

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