2016-03-14 4 views
7

Ich habe diese kleine Probe von Code:Entity Framework mit Asynchron-Controllern in Web api/MVC Entsorgung

public class ValueController : ApiController 
{ 
    private EstateContext _db; 

    public ValueController() 
    { 
     _db = new EstateContext(); 
    } 

    [HttpPost] 
    public async void DoStuff(string id) 
    { 
     var entity = await _db.Estates.FindAsync(id); //now our method goes out and Dispose method is calling 
     //returns here after disposing 
     _db.SaveChanges(); // _db is disposed 

    } 

    protected override void Dispose(bool disposing) 
    { 
     base.Dispose(disposing); 
     _db.Dispose(); 
    } 
} 

Jeder ApiController/Regler implementiert IDisposable-Schnittstelle. In der Dispose-Methode möchte ich also Ressourcen wie DbContext freigeben. Wenn jedoch async verwendet wird, ruft diese Dispose-Methode beim ersten Auftreten von warte auf. Also nach dem Warten habe ich DbContext bereits entsorgt. Also, was ist der beste Weg, EF Kontexte zu entsorgen, wenn Async verwendet wird? Es stellt sich heraus, dass es nicht möglich ist, sich auf die Dispose-Methode im Controller zu verlassen?

+4

'async void'? Das scheint eine schlechte Idee zu sein ... – David

+0

@David hast du mir übrigens viel Zeit gespart)) danke – Wachburn

Antwort

10

Aber wenn async verwendet wird, diese Methode Dispose ruft zunächst das Auftreten von erwarten.

@Konstantins answer ist richtig, aber erlauben Sie mir, etwas darüber zu erklären, warum das passiert. Wenn Sie eine async void-Methode verwenden, erstellen Sie im Grunde eine "fire and forget" -Semantik für Ihren Methodenaufruf, da jeder Aufrufer dieser Methode asynchron nicht selbst darauf mit await warten kann, da er void zurückgibt und keine Form von ein erwartbares (wie ein Task).

Also, obwohl WebAPI asynchrone Methoden unterstützt, scheint es, als ob es eine synchrone void Methode zurückgab, und dann die ASP.NET-Laufzeit fortfährt, um Ihren Controller zu entsorgen, weil er davon ausgeht, dass Sie sind getan mit der Aktion.

Wenn ein Task oder Task<T> aussetzt, sind Sie ausdrücklich den Anrufer „Hören Sie, diese Methode asynchron ist ein schließlich einen Wert in der Zukunft zurückkehren“ erzählt. Die ASP.NET-Laufzeitumgebung weiß, dass der Controller seine Aktion noch nicht beendet hat und wartet auf den tatsächlichen Abschluss der Aktion.

Aus diesem Grund ist ein Aufruf wie folgt:

[HttpPost] 
public async Task DoStuffAsync(string id) 
{ 
    var entity = await _db.Estates.FindAsync(id); 
    _db.SaveChanges(); 
} 

Works.

Als eine Randnotiz - EF DbContext sind so schnell wie möglich verwendet und entsorgt werden sollen. Es ist eine schlechte Idee, sie als globale Variable für mehrere Aktionen zu verwenden, da sie nicht thread-safe sind. Ich würde ein anderes Muster vorschlagen, wo jede Aktion initialisiert und verfügt die DbContext:

[HttpPost] 
public async Task DoStuffAsync(string id) 
{ 
    using (var db = new EstateContext()) 
    { 
     var entity = await db.Estates.FindAsync(id); 
     db.SaveChanges(); 
    } 
} 

Wie von @Wachburn in den Kommentaren, dieser Ansatz ist in der Tat weniger prüfbar. Wenn Sie sicherstellen, dass Ihr Controller und Ihre Aktion nach Abschluss jeder Aktion entsorgt werden und der Kontext nicht wiederverwendet wird, können Sie die DbContext über einen DI-Container injizieren.

+0

danke für die ausführliche Erklärung. Aber im zweiten Teil - was ist mit Injektion und Unit Testing? Wie kann ich solche Controller und Aktionen testen, wenn ich den Kontext nicht einrichten kann? – Wachburn

+0

@Wachburn Sie haben Recht, es ist weniger testbar. Wenn Sie sicherstellen, dass Ihr Controller nach jedem Methodenaufruf entsorgt wird und der Kontext zwischen den Aktionen nicht wiederverwendet wird, können Sie sicher sein, den Ansatz zu verwenden, der einen DI-Container verwendet, um die richtige Abhängigkeit zu injizieren. –

4

Sie müssen eine neue Instanz Ihrer innerhalb der async Methode erstellen.

[HttpPost] 
public async void DoStuff(string id) 
{ 
    EstateContext db = new EstateContext(); 
    var entity = await db.Estates.FindAsync(id); 
    db.SaveChanges(); 
} 

Ich glaube jedoch, dass, wenn Sie den Rückgabetyp der Controllers Aktion Task<ActionResult> ändern, dann sollten Sie in der Lage sein, den Zusammenhang wieder zu verwenden, das ein Mitglied des Controllers ist.

[HttpPost] 
public async Task<ActionResult> DoStuff(string id) 
{ 
    var entity = await _db.Estates.FindAsync(id); 
    _db.SaveChanges(); 
}