2010-07-16 27 views
28

Essendo nuovo per Entity Framework, sono davvero piuttosto bloccato su come procedere con questo insieme di problemi. Sul progetto al quale sto lavorando attualmente, l'intero sito è fortemente integrato con il modello EF. Inizialmente, l'accesso al contesto EF veniva controllato mediante un bootstrapper di Dependency Injection. Per ragioni operative non siamo stati in grado di utilizzare una libreria DI. Ho rimosso questo e ho usato un modello di singole istanze dell'oggetto contesto dove richiesto. Ho iniziato ad ottenere la seguente eccezione:.NET Entity Framework e transazioni

Il tipo "XXX" è stato mappato più di una volta.

Siamo giunti alla conclusione che le diverse istanze del contesto stavano causando questo problema. Ho quindi astratto l'oggetto contesto in una singola istanza statica a cui si accedeva da ciascun thread/pagina. Ora sto ricevendo una delle diverse eccezioni sulle transazioni:

Nuova transazione non consentita perché ci sono altri thread in esecuzione nella sessione.

L'operazione di transazione non può essere eseguita perché ci sono richieste in sospeso che funzionano su questa transazione.

ExecuteReader richiede il comando per eseguire una transazione quando la connessione assegnata al comando si trova in una transazione locale in sospeso. La proprietà Transaction del comando non è stata inizializzata.

L'ultima di queste eccezioni si è verificata in un'operazione di caricamento. Non stavo cercando di salvare lo stato del contesto sul Db sul thread che non funzionava. C'era un altro thread che eseguiva un'operazione del genere comunque.

Queste eccezioni sono intermittenti nella migliore delle ipotesi, ma sono riuscito a far entrare il sito in uno stato in cui le nuove connessioni sono state rifiutate a causa di un blocco delle transazioni. Purtroppo non riesco a trovare i dettagli delle eccezioni.

Credo che la mia prima domanda sia, se il modello EF dovesse essere utilizzato da una singola istanza statica? Inoltre, è possibile rimuovere la necessità di transazioni in EF? Ho provato con un oggetto TransactionScope senza successo ...

Per essere onesti io sono molto bloccato qui, e non riesco a capire il motivo per cui (quello che dovrebbe essere) operazioni piuttosto semplici stanno causando una questione ...

+0

Correlato: http://stackoverflow.com/questions/10585478/one-dbcontext-per-web-request-why – Steven

+0

È un peccato non poter utilizzare un bootstrap IOC, perché la soluzione con [Ninject] (http : //www.ninject.org/) sarebbe quello di associare un'istanza "comune" allo scope _request_, come altri hanno suggerito: 'kernel.Bind >(). > () .InRequestScope(); '- la parte importante è **' InRequestScope' ** – drzaus

risposta

50

La creazione di un valore globale ObjectContext in un'applicazione Web è molto negativa. La classe ObjectContext non è thread-safe. È costruito attorno al concetto di unit of work e questo significa che lo si utilizza per gestire un caso a uso singolo: quindi per una transazione commerciale. È pensato per gestire una singola richiesta.

L'eccezione si verifica perché per ogni richiesta viene creata una nuova transazione, ma si tenta di utilizzare lo stesso ObjectContext. Sei fortunato che lo ObjectContext rilevi questo e lancia un'eccezione, perché ora hai scoperto che questo non funzionerà.

Si prega di pensare a questo perché questo non può funzionare. ObjectContext contiene una cache locale di entità nel database. Ti consente di apportare una serie di modifiche e infine di inviare tali modifiche al database. Quando si utilizza un singolo statico ObjectContext, con più utenti che chiamano SaveChanges su quell'oggetto, come deve sapere che cosa deve essere esattamente impegnato e cosa no?Perché non lo sa, salverà tutte le modifiche, ma a quel punto un altro utente potrebbe ancora apportare modifiche. Quando sei fortunato, EF o il tuo database falliranno, perché le entità sono in uno stato non valido. Se sei sfortunato, gli oggetti che si trovano in uno stato non valido vengono salvati correttamente nel database e potresti scoprire settimane dopo che il tuo database è pieno di merda. La soluzione al tuo problema è create at least one ObjectContext per request. Mentre in teoria è possibile memorizzare nella cache un contesto oggetto nella sessione utente, anche questa è una cattiva idea, perché lo ObjectContext in genere dura troppo a lungo e conterrà dati obsoleti (perché la sua cache interna non verrà aggiornata automaticamente).

UPDATE:

noti inoltre che avere una ObjectContext per thread è male come avente una singola istanza per l'applicazione web completo. ASP.NET utilizza un pool di thread, il che significa che verrà creata una quantità limitata di thread durante la durata di un'applicazione Web. Ciò significa in sostanza che le istanze di ObjectContext in quel caso continueranno a vivere per tutta la durata dell'applicazione, causando gli stessi problemi con la stoltezza dei dati.

Si potrebbe pensare che avere un DbContext per thread sia effettivamente thread-safe, ma di solito non è il caso, dal momento che ASP.NET ha un modello asincrono che consente di terminare le richieste su un thread diverso rispetto a dove è stato avviato (e le ultime versioni di MVC e Web API consentono addirittura un numero arbitrario di thread per gestire una singola richiesta in ordine sequenziale). Ciò significa che il thread che ha avviato una richiesta e creato lo ObjectContext può diventare disponibile per elaborare un'altra richiesta molto prima che la richiesta iniziale sia stata completata. Tuttavia, gli oggetti utilizzati in tale richiesta (come una pagina Web, un controller o qualsiasi classe aziendale) potrebbero comunque fare riferimento a tale ObjectContext. Poiché la nuova richiesta Web viene eseguita nella stessa thread, otterrà la stessa istanza ObjectContext come la vecchia richiesta sta utilizzando. Ciò causa nuovamente condizioni di competizione nell'applicazione e causa gli stessi problemi di sicurezza del thread di ciò che causa un'istanza globale ObjectContext.

+1

Beh, in effetti è quello che pensavo stavo facendo ... Non avrei mai usato normalmente oggetti di contesto dati statici, ma mi stavo disperando. Risulta che ho avuto un altro bug di A N in cui ho avuto singole istanze del contesto.Tuttavia erano all'interno di un oggetto statico, che sembra essere stato la causa. 2 ore della mia vita non stanno tornando ... Grazie – sicknote

+19

Solo due ore? Allora sei un uomo fortunato :-) – Steven

+1

E qual è l'opzione migliore per affrontare questo tipo di problemi? – Romias

4

Come si fa riferimento al "sito" nella tua domanda, presumo che questa sia un'applicazione web. I membri statici esistono una sola volta per l'intera applicazione, se si utilizza un modello di tipo singleton con una singola istanza di contesto nell'intera applicazione, tutti i tipi di richieste si troveranno in tutti i tipi di stati sull'intera applicazione.

Una singola istanza di contesto statico non funzionerà, ma più istanze di contesto per thread saranno problematiche e non è possibile combinare i contesti. Quello di cui hai bisogno è un singolo contesto per thread. Lo abbiamo fatto nella nostra applicazione utilizzando un modello di tipo di iniezione dipendente. Le nostre classi BLL e DAL prendono un contesto come parametro nei metodi, in questo modo si può fare qualcosa di simile di seguito:

using (TransactionScope ts = new TransactionScope()) 
{ 
    using (ObjectContext oContext = new ObjectContext("MyConnection")) 
    { 
     oBLLClass.Update(oEntity, oContext); 
    } 
} 

Se avete bisogno di chiamare altri metodi BLL/DAL entro l'aggiornamento (o qualunque metodo scelto) si passa semplicemente lo stesso contesto. In questo modo gli aggiornamenti/inserimenti/eliminazioni sono atomici, eveything all'interno di un singolo metodo sta utilizzando la stessa istanza di contesto, ma quell'istanza non viene utilizzata da altri thread.