2016-01-13 18 views
10

All'interno di un'applicazione ASP.NET MVC Ricevo il seguente messaggio di errore per uno dei metodi del controller che utilizza il contesto Entity Framework.Perché non compare per impedire la seconda operazione sul contesto EF

Una seconda operazione è iniziata in questo contesto prima di una precedente operazione asincrona completata. Utilizzare 'attendi' per assicurarsi che tutte le operazioni asincrone siano state completate prima di chiamare un altro metodo in questo contesto. Non è garantito che tutti i membri di istanza siano thread-safe.

Sono consapevole che non è possibile eseguire query in parallelo e tutto sembra essere in attesa correttamente. Se eseguo il debug del programma e passo e ispeziono alcuni dei dati restituiti da EF, allora funziona, probabilmente perché questo impone il completamento delle query.

EDIT Se metto un punto di interruzione al momento del check nulla nel metodo di controllo e controllare i dati del shipmentDetail l'eccezione non viene generata.

Ecco uno snippit del codice:

Metodo controller:

[Route("{id:int}/Deliveries")] 
public async Task<ActionResult> DeliveryInfo(int id) 
{ 
    var shipmentDetail = await db.ShipmentDetails.SingleOrDefaultAsync(s => s.Id == id); 
    if (shipmentDetail == null) 
     return HttpNotFound(string.Format("No shipment detail found with id {0}", id)); 
    var model = await DeliveryInfoModel.CreateModel(db, shipmentDetail); 
    return View("DeliveryInfo", model); 
} 

CreateModel Metodo:

public static async Task<DeliveryInfoModel> CreateModel(Context db, ShipmentDetail shipment) 
{ 
    DeliveryInfoModel model = new DeliveryInfoModel() 
    { 
     ShipmentInfo = shipment 
    }; 

    //initialize processing dictionary 
    Dictionary<int, bool> boxesProcessed = new Dictionary<int, bool>(); 
    List<DeliveryBoxStatus> statuses = new List<DeliveryBoxStatus>(); 

    for (int i = 1; i <= shipment.BoxCount; i++) 
     { 
      boxesProcessed.Add(i, false); 
     } 

     //work backwards through process 

     //check for dispositions from this shipment 
     if(shipment.Dispositions.Count > 0) 
     { 
      foreach (var d in shipment.Dispositions) 
      { 
       DeliveryBoxStatus status = new DeliveryBoxStatus() 
       { 
        BoxNumber = d.BoxNumber, 
        LastUpdated = d.Date, 
        Status = d.Type.GetDescription().ToUpper() 
       }; 

       statuses.Add(status); 
       boxesProcessed[d.BoxNumber] = true; 
      } 
     } 

     //return if all boxes have been accounted for 
     if (boxesProcessed.Count(kv => kv.Value) == shipment.BoxCount) 
     { 
      model.BoxStatuses = statuses; 
      return model; 
     } 

     //check for deliveries 
     if(shipment.Job_Detail.Count > 0) 
     { 
      foreach (var j in shipment.Job_Detail.SelectMany(d => d.DeliveryInfos)) 
      { 
       DeliveryBoxStatus status = new DeliveryBoxStatus() 
       { 
        BoxNumber = j.BoxNumber, 
        LastUpdated = j.Job_Detail.To_Client.GetValueOrDefault(), 
        Status = "DELIVERED" 
       }; 

       statuses.Add(status); 
       boxesProcessed[j.BoxNumber] = true; 
      } 
     } 

    //check for items still in processing & where 
    foreach (int boxNum in boxesProcessed.Where(kv => !kv.Value).Select(kv => kv.Key)) 
    { 
     //THIS LINE THROWS THE EXCEPTION 
     var processInfo = await db.Processes.Where(p => p.Jobs__.Equals(shipment.Job.Job__, StringComparison.InvariantCultureIgnoreCase) && p.Shipment == shipment.ShipmentNum && p.Box == boxNum) 
           .OrderByDescending(p => p.date) 
           .FirstOrDefaultAsync(); 

     //process returned data 
     //... 
    } 

    model.BoxStatuses = statuses; 

    return model; 
} 

io non sono del tutto sicuro se è a causa della query eseguita nel controller o a causa delle query eseguite nel ciclo che non sono c completando questo comportamento. C'è qualcosa su cui non capisco quando le query sono effettivamente fatte/restituite a causa della pigrizia di EF, o di come async/await funziona in questa situazione? Ho un sacco di altri metodi di controllo & che effettuano chiamate EF asincrona e non l'ho mai visto prima.

EDIT

mio contesto viene iniettato nel mio controller utilizzando Ninject come il mio contenitore CIO. Ecco la sua configurazione interna del metodo di RegisterServices NinjectWebCommon:

kernel.Bind<Context>().ToSelf().InRequestScope(); 
+0

Ho il sospetto che il vostro errore è venuta da qualche altra parte, questo codice dovrebbe andare bene. A) Il tuo 'db' è condiviso con altre viste/modelli di vista? B) Lo hai scavalcato con un debugger per vedere se getta ancora? – CodingGorilla

+0

A) No non viene condiviso, viene iniettato nel controller e utilizzato solo lì, queste sono le uniche 2 operazioni che utilizzano il contesto 'db' per questa richiesta http. B) L'uso di un debugger non getta se controllo i dati restituiti da EF (ho menzionato questo nella domanda), altrimenti lo fa. – JNYRanger

+0

Puoi approfondire come viene iniettato? Viene iniettato da un contenitore IoC? In tal caso, come viene configurata la registrazione IoC? – CodingGorilla

risposta

6

Evitare lazy loading quando si utilizza asincrone con Entity Framework. Invece, carica i dati che ti servono per prima o usa Include() per assicurarti che i dati necessari siano caricati con la query.

https://msdn.microsoft.com/en-gb/magazine/dn802603.aspx

Stato attuale del Async Support

... Async supporto è stato aggiunto al Entity Framework (nel pacchetto EntityFramework NuGet ) nella versione 6. Dovete fare attenzione a evitare il carico pigro di durante il funzionamento in modo asincrono, poiché il caricamento lento è eseguito sempre in modo sincrono. ...

(enfasi mia)

anche:

https://entityframework.codeplex.com/wikipage?title=Task-based%20Asynchronous%20Pattern%20support%20in%20EF.#ThreadSafety