2016-03-14 14 views
7

ho questo piccolo esempio di codice:Entity Framework smaltimento con i controller asincroni in API Web/MVC

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(); 
    } 
} 

Ogni ApiController/controller implementa l'interfaccia IDisposable. Quindi nel metodo Dispose voglio liberare risorse come DbContext. Ma se si usa async, questo metodo di Dispose chiama al primo occorrenza di attesa. Quindi, dopo l'attesa, DbContext è già disposto. Quindi qual è il modo migliore per disporre di contesti EF quando si usa async? Si scopre che non è possibile fare affidamento sul metodo Dispose nel controller?

+4

'vuoto asincrono'? Sembra una cattiva idea ... – David

+0

@David mi hai risparmiato un sacco di tempo)) grazie – Wachburn

risposta

10

Ma se si utilizza asincrona, questo metodo Dispose richiama in prima occorrenza di attendono.

@Konstantins answer è corretto, ma consentitemi di elaborare un po 'sul perché ciò accade. Quando si utilizza un metodo async void, si sta praticamente creando una semantica "ignora e dimentica" alla chiamata al metodo, poiché qualsiasi chiamante di questo metodo non può attendere esso stesso in modo asincrono con await, poiché restituisce void e non una forma di un aspetto attendibile (come ad esempio Task).

Quindi, anche se WebAPI supporta i metodi asincroni, quando si richiama l'azione sembra un metodo di ritorno sincrono void e quindi il runtime di ASP.NET continua a disporre il controller, poiché presuppone che tu sia fatto con l'azione.

Quando esponendo una Task o Task<T>, stai dicendo esplicitamente il chiamante "Ascolta, questo metodo è asincrona un finirà per restituire un valore in futuro". Il runtime di ASP.NET sa che il tuo controllore non ha ancora terminato di invocare la sua azione e attende il completamento effettivo dell'azione.

Questo è il motivo per una chiamata in questo modo:

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

Works.

Come nota a margine - EF DbContext devono essere utilizzati e smaltiti il ​​prima possibile. Usarli come variabili globali per più azioni è una cattiva idea, poiché non sono thread-safe. Vorrei suggerire un modello diverso, dove ogni azione inizializza e dispone la DbContext:

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

Come sottolineato dagli @Wachburn nei commenti, questo approccio è davvero meno verificabile. Se si assicura che il controller e l'azione siano eliminati dopo che ciascuna azione è stata completata e non è stato riutilizzato il contesto, si è sicuri di iniettare lo DbContext tramite un contenitore DI.

+0

grazie per la spiegazione dettagliata. Ma nella seconda parte, che ne è del test di iniezione e dell'unità? come testare tali controller e azioni se non riesco a configurare il contesto? – Wachburn

+0

@Wachburn Hai ragione, è meno testabile. Se si assicura che il controller venga smaltito dopo ogni chiamata di metodo e non si riutilizzi lo stesso contesto tra le azioni, si è sicuri di utilizzare l'approccio che utilizza un contenitore DI per iniettare la giusta dipendenza. –

4

È necessario creare una nuova istanza di EstateContext all'interno del metodo async.

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

Tuttavia, credo che se si cambia il tipo di ritorno della vostra azione di controllo per Task<ActionResult> allora si dovrebbe essere in grado di riutilizzare il contesto che è un membro del controller.

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