wake-up-neo.com

Entity Framework, DBContext und using () + async?

Es gibt eine Sache, die mich schon lange an Entity Framework nervt.

Letztes Jahr habe ich eine große Bewerbung für einen Kunden mit EF geschrieben. Und während der Entwicklung hat alles super geklappt.

Wir haben das System im August ausgeliefert. Aber nach einigen Wochen sah ich merkwürdige Speicherlecks auf dem Produktionsserver. Mein ASP.NET MVC 4-Prozess beanspruchte nach ein paar Tagen alle Ressourcen des Computers (8 GB). Das war nicht gut. Ich habe im Internet gesucht und festgestellt, dass Sie alle Ihre EF-Abfragen und -Operationen in einem Block using() einschließen sollten, damit der Kontext gelöscht werden kann.

An einem Tag überarbeitete ich meinen gesamten Code, um using() zu verwenden, und dies löste meine Probleme, da der Prozess seitdem auf einer stetigen Speichernutzung beruht.

Der Grund, warum ich meine Abfragen überhaupt nicht umschlossen habe, ist, dass ich meine ersten Controller und Repositorys von Microsofts eigenen Scaffolds gestartet habe, die in Visual Studio enthalten sind. Diese haben die Abfragen nicht mit using umschlossen, sondern hatten stattdessen die Variable DbContext als Instanzvariable des Controllers selbst.

Zuallererst : Wenn es wirklich wichtig ist, über den Kontext zu verfügen (etwas, das nicht sonderbar wäre, die dbconnection muss geschlossen werden und so weiter), sollte Microsoft dies vielleicht in all ihren Beispielen haben!

Jetzt habe ich angefangen, an einem neuen großen Projekt mit all meinen Erkenntnissen im Hinterkopf zu arbeiten, und ich habe die neuen Funktionen von .NET 4.5 und EF 6 async und await ausprobiert. EF 6.0 bietet all diese asynchronen Methoden (z. B. SaveChangesAsync, ToListAsync usw.).

public Task<tblLanguage> Post(tblLanguage language)
{
    using (var langRepo = new TblLanguageRepository(new Entities()))
    {
        return langRepo.Add(RequestOrganizationTypeEnum, language);
    }
}

In der Klasse TblLanguageRepo:

public async Task<tblLanguage> Add(OrganizationTypeEnum requestOrganizationTypeEnum, tblLanguage language)
{
    ...
    await Context.SaveChangesAsync();
    return langaugeDb;
}

Wenn ich meine Anweisungen jetzt jedoch in einen using()-Block einfüge, wird die Ausnahme DbContext was disposed angezeigt, bevor die Abfrage zurückgegeben werden konnte. Dies ist das erwartete Verhalten. Die Abfrage wird asynchron ausgeführt und der Block using wird vor der Abfrage beendet. Aber wie soll ich meinen Kontext auf korrekte Weise entsorgen, während ich die asynchrone Funktion verwende und auf Funktionen von EF 6 warte?

Bitte weisen Sie mich in die richtige Richtung.

Wird in EF 6 using() benötigt? Warum wird dies in den Beispielen von Microsoft nie erwähnt? Wie verwenden Sie asynchrone Funktionen und entsorgen Ihren Kontext ordnungsgemäß?

23
Objective Coder

Dein Code:

public Task<tblLanguage> Post(tblLanguage language)
{
    using (var langRepo = new TblLanguageRepository(new Entities()))
    {
        return langRepo.Add(RequestOrganizationTypeEnum, language);
    }
}

entsorgt das Repository, bevor es eine Task zurückgibt. Wenn Sie den Code async machen:

public async Task<tblLanguage> Post(tblLanguage language)
{
    using (var langRepo = new TblLanguageRepository(new Entities()))
    {
        return await langRepo.Add(RequestOrganizationTypeEnum, language);
    }
}

dann wird das Repository kurz vor dem Abschluss von Task freigegeben. Was tatsächlich passiert, ist, wenn Sie auf await klicken, die Methode gibt ein unvollständiges Task zurück (beachten Sie, dass der using-Block zu diesem Zeitpunkt noch "aktiv" ist). Wenn dann die langRepo.Add-Task abgeschlossen ist, wird die Post-Methode mit der Ausführung fortgesetzt und die langRepo-Instanz freigegeben. Wenn die Post-Methode abgeschlossen ist, ist der zurückgegebene Task abgeschlossen.

Weitere Informationen finden Sie unter mein async Intro .

24
Stephen Cleary

Ich würde den Weg 'ein DbContext pro Anfrage' wählen und den DbContext innerhalb der Anfrage wiederverwenden. Da alle Aufgaben ohnehin am Ende der Anfrage erledigt sein sollten, können Sie diese erneut sicher entsorgen.

Siehe dazu: Ein DbContext pro Anforderung in ASP.NET MVC (ohne IOC - Container)

Einige andere Vorteile:

  • einige Entitäten werden möglicherweise bereits im DbContext aus vorherigen Abfragen erstellt. Einige zusätzliche Abfragen werden gespeichert.
  • sie haben nicht alle diese zusätzlichen using-Anweisungen, die Ihren Code überladen.
4
Dirk Boer

Ich stimme @Dirk Boer zu , dass der beste Weg zur Verwaltung der Lebensdauer von DbContext die Verwendung eines IoC-Containers ist, der über den Kontext verfügt, wenn die http-Anforderung abgeschlossen ist. Wenn dies jedoch keine Option ist, können Sie auch Folgendes tun:

var dbContext = new MyDbContext();
var results = await dbContext.Set<MyEntity>.ToArrayAsync();
dbContext.Dispose();

Die using-Anweisung ist nur ein syntaktischer Zucker für die Beseitigung eines Objekts am Ende eines Codeblocks. Sie können denselben Effekt auch ohne einen using-Block erzielen, indem Sie einfach .Dispose selbst aufrufen.

Wenn Sie darüber nachdenken, sollten Sie keine objektbezogenen Ausnahmen erhalten, wenn Sie das Schlüsselwort await im using-Block verwenden:

public async Task<tblLanguage> Post(tblLanguage language)
{
    using (var langRepo = new TblLanguageRepository(new Entities()))
    {
        var returnValue = langRepo.Add(RequestOrganizationTypeEnum, language);
        await langRepo.SaveChangesAsync();
        return returnValue;
    }
}
1
danludwig

Wenn Sie richtige n-stufige Programmiermuster verwenden, sollte Ihr Controller niemals wissen, dass eine Datenbankanforderung gestellt wird. Das sollte alles in Ihrer Serviceebene passieren.

Dafür gibt es mehrere Möglichkeiten. Eine ist das Erstellen von 2 Konstruktoren pro Klasse, einer, der einen Kontext erstellt, und einer, der einen bereits vorhandenen Kontext akzeptiert. Auf diese Weise können Sie den Kontext herumgeben, wenn Sie sich bereits in der Service-Schicht befinden, oder einen neuen erstellen, wenn der Controller/das Modell die Service-Ebene aufruft.

Die andere besteht darin, eine interne Überladung jeder Methode zu erstellen und den Kontext dort zu akzeptieren. 

Aber ja, Sie sollten diese in eine Verwendung einwickeln.

Theoretisch sollte die Müllsammlung diese sauber machen, ohne sie einzuwickeln, aber ich traue dem GC nicht ganz. 

1
Scottie

Wenn Sie Ihre Methode synchron halten möchten, aber asynchron in DB speichern möchten, verwenden Sie die using-Anweisung nicht. Wie @danludwig sagte, handelt es sich nur um einen syntaktischen Zucker. Sie können die SaveChangesAsync () - Methode aufrufen und den Kontext nach Abschluss der Aufgabe bereitstellen. Eine Möglichkeit, dies zu tun, ist folgende:

//Save asynchronously then dispose the context after
context.SaveChangesAsync().ContinueWith(c => context.Dispose());

Beachten Sie, dass das Lambda, das Sie an ContinueWith () übergeben, auch asynchron ausgeführt wird.

0
Ross Brigoli

IMHO, es ist wieder ein Problem durch die Verwendung von Lazy-Loading. Nachdem Sie Ihren Kontext festgelegt haben, können Sie eine Eigenschaft nicht mehr faul laden, da durch die Anordnung des Kontexts die zugrunde liegende Verbindung zum Datenbankserver geschlossen wird.

Wenn Sie Lazy-Loading aktiviert haben und die Ausnahme nach using scope auftritt, lesen Sie bitte https://stackoverflow.com/a/21406579/870604

0
ken2k