Considerazioni relative alle prestazioni (Entity Framework)
In questo argomento vengono descritte le caratteristiche di prestazione di ADO.NET Entity Framework e vengono illustrate alcune considerazioni per migliorare le prestazioni di applicazioni Entity Framework.
Fasi di esecuzione di query
Per capire meglio le prestazioni delle query in Entity Framework, è utile capire le operazioni che si verificano quando una query viene eseguita su un modello concettuale e restituisce dati come oggetti. Nella tabella seguente viene descritta questa serie di operazioni.
Operazione | Costo relativo | Frequenza | Commenti |
---|---|---|---|
Caricamento di metadati |
Moderato |
Una volta in ogni dominio dell'applicazione. |
I metadati del modello e di mapping utilizzati da Entity Framework sono caricati in un MetadataWorkspace. Questi metadati sono memorizzati nella cache globalmente e sono disponibili per altre istanze di ObjectContext nello stesso dominio dell'applicazione. |
Apertura della connessione al database |
Moderato1 |
Secondo le necessità. |
Poiché una connessione aperta al database utilizza una risorsa preziosa, Entity Framework apre e chiude la connessione al database solo in base alle necessità. È possibile aprire anche in modo esplicito la connessione. Per ulteriori informazioni, vedere Gestione di connessioni e transazioni (Entity Framework). |
Generazione di visualizzazioni |
Alto |
Una volta in ogni dominio dell'applicazione. Possono essere generate anticipatamente. |
Prima di poter eseguire una query su un modello concettuale o salvare delle modifiche all'origine dati, Entity Framework deve generare un set di visualizzazioni query locali per accedere al database. A causa del costo elevato della generazione di queste visualizzazioni, è possibile generarle in anticipo e aggiungerle al progetto in fase di progettazione. Per ulteriori informazioni, vedere Procedura: pregenerare le visualizzazioni per migliorare le prestazioni di esecuzione delle query. |
Preparazione della query |
Moderato2 |
Una volta per ogni query univoca. |
Include i costi per creare il comando della query, generare una struttura ad albero dei comandi basata sui metadati del modello e di mapping e definire la forma dei dati restituiti. Poiché vengono memorizzati nella cache, le successive esecuzioni dei comandi della query Entity SQL sono più veloci. È possibile utilizzare anche query LINQ compilate per ridurre questo costo nelle esecuzioni successive. Per ulteriori informazioni, vedere Query compilate (LINQ to Entities). Per informazioni generali sull'esecuzione di query LINQ, vedere LINQ to Entities. |
Esecuzione della query |
Basso2 |
Una volta per ogni query. |
Costo dell'esecuzione del comando sull'origine dati tramite il provider di dati ADO.NET. Poiché la maggior parte delle origini dati memorizzano nella cache i piani di query, è possibile che le successive esecuzioni della stessa query siano ancor più veloci. |
Caricamento e convalida di tipi |
Basso3 |
Una volta per ciascuna istanza ObjectContext. |
I tipi vengono caricati e convalidati rispetto ai tipi definiti nel modello concettuale. |
Rilevamento |
Basso3 |
Una volta per ogni oggetto restituito da una query. 4 |
Se una query utilizza l'opzione di unione NoTracking, questa fase non influisce sulle prestazioni. Se la query utilizza l'opzione di unione AppendOnly, PreserveChanges o OverwriteChanges, i risultati della query vengono rilevati nell'oggetto ObjectStateManager. Un oggetto EntityKey viene generato per ogni oggetto rilevato che la query restituisce e viene utilizzato per creare un oggetto ObjectStateEntry in ObjectStateManager. Se è possibile trovare un oggetto ObjectStateEntry per EntityKey, viene restituito l'oggetto esistente. Se viene utilizzata l'opzione PreserveChanges o OverwriteChanges, l'oggetto viene aggiornato prima di essere restituito. Per ulteriori informazioni, vedere Risoluzione di identità, gestione dello stato e rilevamento delle modifiche (Entity Framework). |
Materializzazione degli oggetti |
Moderato3 |
Una volta per ogni oggetto restituito da una query. 4 |
Processo di lettura dell'oggetto DbDataReader restituito, di creazione di oggetti e di impostazione di valori di proprietà che si basano sui valori in ciascuna istanza della classe DbDataRecord. Se l'oggetto esiste già in ObjectContext e la query utilizza l'opzione di unione AppendOnly o PreserveChanges, questa fase non influisce sulle prestazioni. Per ulteriori informazioni, vedere Risoluzione di identità, gestione dello stato e rilevamento delle modifiche (Entity Framework). |
1 Quando un provider dell'origine dati implementa pool di connessioni, il costo di apertura di una connessione è distribuito nel pool. Il provider .NET per SQL Server supporta i pool di connessioni.
2 Il costo aumenta con la maggiore complessità della query.
3 Il costo totale aumenta proporzionalmente al numero di oggetti restituiti dalla query.
4 Questo overhead non è richiesto per le query EntityClient perché le query EntityClient restituiscono un EntityDataReader anziché oggetti. Per ulteriori informazioni, vedere Provider EntityClient per Entity Framework.
Considerazioni aggiuntive
Di seguito sono illustrate altre considerazioni che possono influire sulle prestazioni di applicazioni Entity Framework.
Esecuzione di query
Poiché le query possono richiedere l'utilizzo intenso delle risorse, è bene considerare in quale punto del codice e in quale computer viene eseguita una query.
Esecuzione posticipata e immediata
Quando si crea un oggetto ObjectQuery o una query LINQ, è possibile che la query non sia eseguita immediatamente. L'esecuzione della query è rinviata fino a quando i risultati non diventano necessari, ad esempio durante un'enumerazione foreach (C#) o For Each (Visual Basic) o quando è assegnata per completare una raccolta List. L'esecuzione della query inizia immediatamente quando si chiama il metodo Execute in un oggetto ObjectQuery o quando si chiama un metodo LINQ che restituisce una query Singleton, ad esempio First o Any. Per ulteriori informazioni, vedere Query di oggetto (Entity Framework) e Esecuzione di query (LINQ to Entities).
Esecuzione di query LINQ sul lato client
Sebbene l'esecuzione di una query LINQ avvenga nel computer che ospita l'origine dati, è possibile che alcune parti della query LINQ vengano valutate nel computer client. Per ulteriori informazioni, vedere la sezione Esecuzione nell'archivio di Esecuzione di query (LINQ to Entities).
Complessità delle query e del mapping
La complessità di query singole e del mapping nel modello dell'entità influirà in modo significativo sulle prestazioni delle query.
Complessità del mapping
I modelli che sono più complessi di un mapping uno a uno tra entità nel modello concettuale e tabelle nel modello di archiviazione generano comandi più complessi rispetto ai modelli che dispongono di un mapping uno a uno.
Complessità delle query
Le query con un elevato numero di join nei comandi che sono eseguite sull'origine dati o che restituiscono una grande quantità di dati influiscono sulle prestazioni nei seguenti modi:
Le query su un modello concettuale che sembrano semplici possono comportare l'esecuzione di query più complesse sull'origine dati. Il motivo consiste nel fatto che Entity Framework traduce una query su un modello concettuale in una equivalente query sull'origine dati. Quando un singolo set di entità nel modello concettuale esegue il mapping a più tabelle nell'origine dati, o quando una relazione tra entità viene mappata a una tabella di join, è possibile che il comando di query eseguito sulla query dell'origine dati richieda uno o più join.
Nota: Utilizzare il metodo ToTraceString della classe ObjectQuery o EntityCommand per visualizzare i comandi che sono eseguiti sull'origine dati per una data query.Per ulteriori informazioni, vedere Procedura: visualizzare i comandi di archiviazione (Entity Framework). Le query Entity SQL annidate possono creare join nel server e possono restituire un elevato numero di righe.
Di seguito è riportato un esempio di query annidata in una clausola di proiezione:
SELECT c, (SELECT c, (SELECT c FROM AdventureWorksModel.Vendor AS c ) As Inner2 FROM AdventureWorksModel.JobCandidate AS c ) As Inner1 FROM AdventureWorksModel.EmployeeDepartmentHistory AS c
Inoltre, tali query fanno in modo che la pipeline delle query generi un'unica query con duplicazione di oggetti tra le query annidate. Per questo motivo una singola colonna può essere duplicata più volte. In alcuni database, tra cui SQL Server, questo può causare un forte aumento delle dimensioni della tabella TempDB, con effetti negativi sulle prestazioni del server. Occorre quindi prestare attenzione quando si eseguono query annidate.
Qualsiasi query che restituisce un elevato numero di dati può causare una diminuzione delle prestazioni se il client in quel momento sta eseguendo operazioni che consumano risorse in modo proporzionale alle dimensioni del set dei risultati. In tali casi, è necessario limitare la quantità di dati restituiti dalla query. Per ulteriori informazioni, vedere Procedura: paging dei risultati delle query (Entity Framework).
Qualsiasi comando generato automaticamente da Entity Framework può essere più complesso degli analoghi comandi scritti in modo esplicito da un sviluppatore del database. Se è necessario avere il controllo esplicito sui comandi eseguiti sull'origine dati, si può definire un mapping a una funzione con valori di tabella o una stored procedure.
Relazioni
Per ottenere prestazioni ottimali nell'esecuzione delle query, è necessario definire delle relazioni tra entità sia come associazioni nel modello di entità che come relazioni logiche nell'origine dati.
Percorsi della query
Per impostazione predefinita, quando si esegue un ObjectQuery, gli oggetti correlati non vengono restituiti (anche se si tratta di oggetti che rappresentano le relazioni). È possibile caricare oggetti correlati in una delle tre modalità riportate di seguito:
Impostando il percorso della query prima che venga eseguito l'oggetto ObjectQuery.
Chiamando il metodo Load sulla proprietà di navigazione esposta dall'oggetto.
Impostando l'opzione LazyLoadingEnabled nell'oggetto ObjectContext su true. Si noti che questa operazione viene eseguita automaticamente quando si genera codice del livello oggetti con Entity Data Model Designer. Per ulteriori informazioni, vedere Generated Code Overview.
Nella scelta dell'opzione da utilizzare, considerare il compromesso tra il numero di richieste nel database e la quantità di dati restituiti in una singola query. Per ulteriori informazioni, vedere Caricamento di oggetti correlati (Entity Framework).
Utilizzo di percorsi della query
I percorsi della query definiscono il grafico degli oggetti restituiti da una query. Quando si definisce un percorso della query, è sufficiente una sola richiesta al database per restituire tutti gli oggetti definiti dal percorso. L'utilizzo di percorsi della query può comportare l'esecuzione di comandi complessi nell'origine dati, derivanti da query di oggetto apparentemente semplici. Questo si verifica in quanto per restituire oggetti correlati in una singola query sono necessari uno o più join. Questa complessità è maggiore nelle query su un modello di entità complesso, ad esempio un'entità con ereditarietà o un percorso che include relazioni molti-a-molti.
Nota: |
---|
Utilizzare il metodo ToTraceString per vedere il comando che verrà generato da ObjectQuery.Per ulteriori informazioni, vedere Procedura: visualizzare i comandi di archiviazione (Entity Framework). |
Quando un percorso della query include troppi oggetti correlati o gli oggetti contengono troppi dati delle righe, potrebbe non essere possibile completare la query dall'origine dati. Questo si verifica se la query richiede un'archiviazione temporanea intermedia superiore alle possibilità dell'origine dati. In questo caso, è possibile ridurre la complessità della query sull'origine dati caricando in modo esplicito gli oggetti correlati.
Caricamento esplicito di oggetti correlati
È possibile caricare in modo esplicito degli oggetti correlati chiamando il metodo Load in una proprietà di navigazione che restituisce un oggetto EntityCollection o EntityReference. Il caricamento esplicito di oggetti richiede un round trip al database ad ogni chiamata del metodo Load.
Nota: |
---|
Se si chiama il metodo Load durante l'esecuzione di ciclo in una raccolta di oggetti restituiti, come quando si utilizza l'istruzione foreach (For Each in Visual Basic), è necessario che il provider specifico dell'origine dati supporti più set dei risultati attivi in un'unica connessione.Per un database di SQL Server, è necessario specificare un valore di MultipleActiveResultSets = true nella stringa di connessione del provider.
|
È possibile utilizzare anche il metodo LoadProperty quando non si dispone di proprietà EntityCollection o EntityReference sulle entità. Questa situazione si rivela utile quando si utilizzano entità POCO.
Anche se il caricamento esplicito di oggetti correlati ridurrà il numero di join e ridurrà la quantità di dati ridondanti, Load richiede ripetute connessioni al database e questa procedura può diventare costosa se si caricano in modo esplicito molti oggetti.
Salvataggio delle modifiche
Quando si chiama il metodo SaveChanges in un oggetto ObjectContext, viene generato un comando di creazione, aggiornamento o eliminazione distinto per ogni oggetto aggiunto, aggiornato o eliminato nel contesto. Questi comandi vengono eseguiti sull'origine dati in una sola transazione. Come avviene per le query, le prestazioni delle operazioni di creazione, aggiornamento ed eliminazione dipendono dalla complessità del mapping nel modello concettuale.
Transazioni distribuite
Le operazioni in una transazione esplicita che richiedono risorse gestite dal DTC (Distributed Transaction Coordinator) saranno molto più costose di un'operazione analoga che non richiede il DTC. Una promozione al DTC avverrà nelle situazioni seguenti:
Una transazione esplicita con un'operazione su un database SQL Server 2000 o altra origine dati che promuove sempre transazioni esplicite al DTC.
Una transazione esplicita con un'operazione a SQL Server 2005 quando la connessione è gestita da Entity Framework . Ciò si verifica perché SQL Server 2005 promuove a un DTC ogni qualvolta una connessione viene chiusa e riaperta all'interno di una singola transazione, che è il comportamento predefinito di Entity Framework . Questa promozione al DTC non avviene quando si utilizza SQL Server 2008. Per evitare questa promozione quando si utilizza SQL Server 2005, è necessario aprire e chiudere la connessione in modo esplicito all'interno della transazione. Per ulteriori informazioni, vedere Gestione di connessioni e transazioni (Entity Framework).
Una transazione esplicita viene utilizzata quando una o più operazioni vengono eseguite all'interno di una transazione System.Transactions. Per ulteriori informazioni, vedere Gestione di connessioni e transazioni (Entity Framework).
Strategie per migliorare le prestazioni
È possibile migliorare le prestazioni complessive delle query in Entity Framework utilizzando le strategie seguenti.
Generare in anticipo le visualizzazioni
La generazione di visualizzazioni basate su un modello di entità ha un costo significativo la prima volta che un'applicazione esegue una query. Utilizzare l'utilità EdmGen.exe per generare in anticipo visualizzazioni come un file di codice Visual Basic o C# che può essere aggiunto al progetto nella fase di progettazione. Le visualizzazioni generate in anticipo vengono convalidate in fase di runtime per garantirne la coerenza con la versione corrente del modello di entità specificato. Per ulteriori informazioni, vedere Procedura: pregenerare le visualizzazioni per migliorare le prestazioni di esecuzione delle query.
Utilizzare l'opzione di unione NoTracking per le query
Esiste un costo necessario per tenere traccia degli oggetti restituiti nel contesto dell'oggetto. Il rilevamento di modifiche agli oggetti e la garanzia che più richieste per la stessa l'entità logica restituiscano la stessa istanza dell'oggetto richiedono che gli oggetti siano allegati a un'istanza ObjectContext. Se non si intende effettuare aggiornamenti o eliminazioni di oggetti e non si richiede la gestione di identità, può essere utile utilizzare le opzioni di unione NoTracking per l'esecuzione di query.
Utilizzare query LINQ compilate
Quando si utilizza un'applicazione che esegue molte volte query strutturalmente simili in Entity Framework, è spesso possibile migliorare le prestazioni compilando la query una volta ed eseguendola più volte con parametri diversi. Un'applicazione potrebbe ad esempio essere utilizzata per recuperare tutti i clienti di una determinata città, specificata in fase di runtime dall'utente in un modulo. In LINQ to Entities è supportato l'utilizzo di query compilate per questo scopo. La query viene compilata solo una volta durante la prima esecuzione. Le opzioni di unione impostate per la query durante la compilazione non possono essere modificate successivamente.
Per ulteriori informazioni, vedere Query compilate (LINQ to Entities).
Restituire la quantità corretta di dati
In alcuni scenari, la specifica di un percorso della query utilizzando il metodo Include è molto più veloce perché richiede meno round trip al database. Tuttavia, in altri scenari, l'esecuzione di round trip aggiuntivi al database per caricare oggetti correlati potrebbe risultare più veloce perché le query più semplici con meno join comportano meno ridondanza di dati. Per questo motivo si consiglia di testare le prestazioni utilizzando varie modalità di recupero di oggetti correlati. Per ulteriori informazioni, vedere Caricamento di oggetti correlati (Entity Framework).
Per evitare la restituzione di troppi dati in una sola query, si può considerare di eseguire il paging dei risultati della query in gruppi più gestibili. Per ulteriori informazioni, vedere Procedura: paging dei risultati delle query (Entity Framework).
Limitare l'ambito di ObjectContext
Nella maggior parte dei casi, è necessario creare un'istanza ObjectContext all'interno di un'istruzione using
(Using…End Using
in Visual Basic). In questo modo le prestazioni migliorano in quanto viene garantita l'eliminazione automatica delle risorse associate al contesto dell'oggetto quando il codice esce dal blocco di istruzioni. Tuttavia, quando i controlli vengono associati a oggetti gestiti dal contesto dell'oggetto, l'istanza ObjectContext deve essere gestita finché l'associazione è necessaria e viene eliminata manualmente. Per ulteriori informazioni, vedere Gestione di connessioni e transazioni (Entity Framework).
Aprire manualmente la connessione al database
Quando l'applicazione esegue una serie di query di oggetto o chiama spesso SaveChanges per operazioni di creazione, aggiornamento ed eliminazione permanente all'origine dati, Entity Framework deve continuamente aprire e chiudere la connessione all'origine dati. In queste situazioni può essere utile aprire manualmente la connessione all'inizio di queste operazioni e chiuderla o eliminarla quando le operazioni sono completate. Per ulteriori informazioni, vedere Gestione di connessioni e transazioni (Entity Framework).
Dati sulle prestazioni
Alcuni dati relativi alle prestazioni di Entity Framework sono pubblicati nei post seguenti nel blog del team di ADO.NET:
Analisi delle prestazioni di ADO.NET Entity Framework - parte 1
Analisi delle prestazioni di ADO.NET Entity Framework - parte 2
Vedere anche
Concetti
Considerazioni sullo sviluppo e sulla distribuzione (Entity Framework)