Entity Framework e pool di connessioni


268

Di recente ho iniziato a utilizzare Entity Framework 4.0 nella mia applicazione .NET 4.0 e sono curioso di sapere alcune cose relative al pooling.

  1. Il pool di connessioni, come so, è gestito dal fornitore di dati ADO.NET, nel mio caso quello del server MS SQL. Questo si applica quando si crea un'istanza di un nuovo contesto di entità ( ObjectContext), cioè senza parametri new MyDatabaseModelEntities()?

  2. Quali sono i vantaggi e gli svantaggi di a) creare un contesto di entità globale per l'applicazione (es. Un'istanza statica) oppure b) creare ed esporre un contesto di entità per ogni data operazione / metodo, con un usingblocco.

  3. Altre raccomandazioni, migliori pratiche o approcci comuni per determinati scenari che dovrei conoscere?

Risposte:


369
  1. Il pool di connessioni viene gestito come in qualsiasi altra applicazione ADO.NET. La connessione entità utilizza ancora la connessione al database tradizionale con una stringa di connessione tradizionale. Credo che tu possa disattivare il pool di connessioni nella stringa di connessione se non vuoi usarlo. (leggi di più sul pool di connessioni di SQL Server (ADO.NET) )
  2. Mai e poi mai usare il contesto globale. ObjectContext implementa internamente diversi modelli tra cui Identity Map e Unit of Work. L'impatto dell'utilizzo del contesto globale è diverso per tipo di applicazione.
  3. Per le applicazioni Web utilizzare un unico contesto per richiesta. Per i servizi Web utilizzare un unico contesto per chiamata. Nell'applicazione WinForms o WPF utilizzare un singolo contesto per modulo o per presentatore. Ci possono essere alcuni requisiti speciali che non permetteranno di usare questo approccio, ma nella maggior parte dei casi questo è sufficiente.

Se vuoi sapere quale impatto ha il contesto di un singolo oggetto per l'applicazione WPF / WinForm, consulta questo articolo . Si tratta di NHibernate Session ma l'idea è la stessa.

Modificare:

Quando si utilizza EF, per impostazione predefinita carica ogni entità una sola volta per contesto. La prima query crea un'istanza dell'entità e la memorizza internamente. Qualsiasi query successiva che richiede un'entità con la stessa chiave restituisce questa istanza memorizzata. Se i valori nell'archivio dati sono cambiati, si riceve comunque l'entità con i valori dalla query iniziale. Questo si chiama modello di mappa delle identità . È possibile forzare il contesto dell'oggetto per ricaricare l'entità ma ricaricherà una singola istanza condivisa.

Eventuali modifiche apportate all'entità non vengono mantenute finché non si richiama SaveChangesil contesto. È possibile apportare modifiche in più entità e archiviarle contemporaneamente. Questo si chiama modello di unità di lavoro . Non puoi dire in modo selettivo quale entità collegata modificata desideri salvare.

Combina questi due schemi e vedrai alcuni effetti interessanti. Hai solo un'istanza di entità per l'intera applicazione. Eventuali modifiche all'entità influenzano l'intera applicazione anche se le modifiche non sono ancora persistite (confermate). Nella maggior parte dei casi questo non è ciò che desideri. Supponiamo di avere un modulo di modifica nell'applicazione WPF. Stai lavorando con l'entità e decidi di annullare la modifica complessa (modifica dei valori, aggiunta di entità correlate, rimozione di altre entità correlate, ecc.). Ma l'entità è già modificata nel contesto condiviso. Cosa farai? Suggerimento: non conosco alcun CancelChanges o UndoChanges attivo ObjectContext.

Penso che non dobbiamo discutere lo scenario del server. La semplice condivisione di una singola entità tra più richieste HTTP o chiamate al servizio Web rende la tua applicazione inutile. Qualsiasi richiesta può semplicemente attivare SaveChangese salvare dati parziali da un'altra richiesta perché stai condividendo una singola unità di lavoro tra tutte. Ciò avrà anche un altro problema: il contesto e qualsiasi manipolazione con entità nel contesto o una connessione al database utilizzata dal contesto non sono thread-safe.

Anche per un'applicazione di sola lettura un contesto globale non è una buona scelta perché probabilmente desideri nuovi dati ogni volta che esegui una query sull'applicazione.


Grazie per la tua risposta. Forse potresti approfondire il motivo per cui è male usare un singolo contesto globale? Rende sicuramente più difficile l'accesso parallelo, ma cos'altro ...?
Noldorin,

Ok, ora è molto più chiaro, grazie. Solo per confermare, sebbene un contesto globale non sia mai veramente appropriato, un singolo contesto per una "finestra di dialogo di modifica" o tale potrebbe essere la strada giusta? In altre situazioni, come i servizi Web e ASP.NET, i contesti all'interno dei metodi hanno più senso. Circa corretto?
Noldorin,

Ho seguito il tuo consiglio e rimosso il singelton. Ora ricevo un altro errore: stackoverflow.com/questions/14795899/…
Elad Benda

Comprendo che l'implementazione del modello Unit of work e l'incapsulamento di DbContext dovrebbero separare la logica di business e le operazioni del database. Non sono in grado di capire come implementare il modello Unità di lavoro e come utilizzare TransactionScope solo per alcune operazioni.
Rudolf Dvoracek,

4
@RudolfDvoracek: facilmente. TransactionScopenon appartiene all'unità di lavoro, appartiene alla tua logica aziendale perché la logica stessa definisce la transazione. L'unità di lavoro definisce solo ciò che deve essere mantenuto insieme mentre l'ambito della transazione consente di utilizzare la persistenza dell'unità di lavoro più volte all'interno della stessa transazione.
Ladislav Mrnka,

70

Secondo Daniel Simmons:

Creare una nuova istanza ObjectContext in un'istruzione Using per ciascun metodo di servizio in modo che venga eliminata prima che il metodo ritorni. Questo passaggio è fondamentale per la scalabilità del tuo servizio. Assicura che le connessioni al database non vengano mantenute aperte durante le chiamate di servizio e che lo stato temporaneo utilizzato da una particolare operazione venga garbage collection al termine di tale operazione. Entity Framework memorizza automaticamente nella cache i metadati e le altre informazioni necessarie nel dominio dell'app e le connessioni al database dei pool ADO.NET, quindi ricreare il contesto ogni volta è un'operazione rapida.

Questo è dal suo articolo completo qui:

http://msdn.microsoft.com/en-us/magazine/ee335715.aspx

Credo che questo consiglio si estenda alle richieste HTTP, quindi sarebbe valido per ASP.NET. Un'applicazione stateful, client fat come un'applicazione WPF potrebbe essere l'unico caso per un contesto "condiviso".


Grazie, è una citazione molto istruttiva lì. Tuttavia, mi chiedo ancora se un contesto condiviso (globale) sarebbe appropriato anche per un'app WPF client o simile. C'è qualche vantaggio anche in questo caso?
Noldorin,

Non ci sarebbe alcun vantaggio in un contesto globale in un'app WPF, ma probabilmente non ci sarebbe neppure un danno significativo. Se si implementa un contesto globale, potrebbe essere necessario gestire manualmente le connessioni al database (chiusura esplicita della connessione) in caso di alti tassi di richieste.
Dave Swersky,

1
Destra; quindi essenzialmente non posso mai sbagliarmi usando più contesti temporanei (dato che so che sta avvenendo il pool di connessioni)? ... Se stessi usando un unico contesto globale, la connessione in teoria non potrebbe cadere in un momento casuale?
Noldorin,

1
@Nolodrin: non credo che la connessione cadrà "casualmente" ... il rischio è che le connessioni possano rimanere aperte troppo a lungo e saturare il pool di connessioni.
Dave Swersky,

1
A mio avviso IDisposable, l' attrezzo ObjectContext / DbContext , quindi dovrebbe essere aperto per il minor tempo ragionevole.
nicodemus13,

12

Accedere alla documentazione EF6 (4,5 anche): https://msdn.microsoft.com/en-us/data/hh949853#9

9.3 Contesto per richiesta

I contesti di Entity Framework sono pensati per essere utilizzati come istanze di breve durata al fine di fornire l'esperienza di prestazione ottimale . I contesti dovrebbero essere di breve durata e scartati, e come tali sono stati implementati per essere molto leggeri e riutilizzare i metadati ogni volta che è possibile. Negli scenari Web è importante tenerlo presente e non avere un contesto per più della durata di una singola richiesta. Allo stesso modo, in scenari non web, il contesto deve essere scartato in base alla comprensione dei diversi livelli di memorizzazione nella cache in Entity Framework. In generale, si dovrebbe evitare di avere un'istanza di contesto per tutta la vita dell'applicazione, nonché contesti per thread e contesti statici.


2
So che questa risposta è qui da un po ', ma devo dire che questo mi ha fatto risparmiare un sacco di mal di testa. Ho continuato a ricevere l'errore "Connessione in pool" quando si utilizza EF con Oracle e non sono riuscito a capire il perché. Avevo impostato dbContext come variabile di classe, istanziandolo alla creazione. Modificandolo per creare il contesto, se necessario, risolto tutti i mali del mio mondo. Grazie!
Fletchius,

1

Il codice seguente ha aiutato il mio oggetto ad essere aggiornato con nuovi valori del database. Il comando Entry (oggetto) .Reload () impone all'oggetto di richiamare i valori del database

GM_MEMBERS member = DatabaseObjectContext.GM_MEMBERS.FirstOrDefault(p => p.Username == username && p.ApplicationName == this.ApplicationName);
DatabaseObjectContext.Entry(member).Reload();

così come per le collezioni (codice VB):CType(myContext, IObjectContextAdapter).ObjectContext.Refresh(RefreshMode.StoreWins,myCustomers)
Ivan Ferrer Villa,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.