Ottimizzare le performance di un'applicazione che utilizza Entity Framework

di Stefano Mostarda, in LINQ,

Introduzione

Quando si utilizza un ORM, ci sono molte cose che si possono personalizzare o fare in maniera diversa al fine ottimizzare l'accesso ai dati. Entity Framework non viene meno a questa affermazione e offre molti punti in cui il codice può essere ottimizzato.

Il primo punto di ottimizzazione consiste nel codice SQL che viene inviato al server per eseguire una query. Vista la dinamicità e la versatilità offerta da LINQ to Entities nella composizione delle query, il generatore di codice deve essere altrettanto flessibile e questo in alcuni casi porta alla generazione di codice SQL estremamente verboso e pesante. Quello che si può fare è scrivere delle stored procedure apposite per ottimizzare alcune query.

Il secondo punto di ottimizzazione coinvolge sempre le stored procedure ma questa volta per quanto riguarda gli aggiornamenti. Infatti, qualora si debbano eseguire comandi aggiuntivi oltre a quelli generati da Entity Framework per gestire la persistenza di dati, si può ricorrere a stored procedure per diminuire così il numero di chiamate al database.

Un altro punto in cui si può guadagnare in performance è nella modalità di utilizzo dei dati ottenuti da una query. Il processo di trasformazione dai dati in oggetti spesso è inutile per quello che si vuole ottenere quindi lavorare direttamente sui dati semi-grezzi restituiti dalla query può far guadagnare tempo prezioso laddove le performance sono fondamentali.

Naturalmente esistono anche altre possibilità come il caching delle query scritte tramite Entity SQL, la precompilazione delle query LINQ to Entities e la disabilitazione del tracking degli oggetti.

Ora che si ha una visione di insieme dei punti che verranno trattati nell'articolo, è ora di cominciare a parlare del primo punto ovvero come creare stored procedure per ottimizzare le query.

Ottimizzare il codice SQL delle query

Ottimizzare il codice SQL lanciato verso il database per ritrovare oggetti è la prima vera ottimizzazione da fare in Entity Framework. Se da un lato è vero che LINQ to Entities ed Entity SQL astraggono completamente l'esistenza del database permettendo query direttamente sulle classi, dall'altro lato diventano pericolosi perché la loro versatilità a volte è un limite. Infatti, a volte capita che, per mantenere la versatilità, il codice SQL generato diventi sproporzionato rispetto a quello che deve fare.

In questi casi, l'utilizzo di una stored procedure diventa d'obbligo soprattutto nei casi in cui la query venga eseguita molto spesso dal sistema. Si prenda ad esempio il seguente schema:

Scenario1

Se si vogliono estrarre tutti i veicoli, la query scritta con LINQ to Entities è:

using (var ctx = new DBContext()){
 var vehicles = ctx.Vehicles;
}

Tramite il tool SQL profiler, oppure invocando il metodo ToTraceString sulla variabile vehicles, si può verificare il codice SQL generato da Entity Framework. Il codice che ci si aspetterebbe più o meno è il seguente:

SELECT * --seleziona tutti i campi solo per semplicità
FROM Vehicle v
JOIN car c on (v.VehicleId = c.VehicleId)
JOIN motorcycle m on (v.VehicleId = c.VehicleId)

Entity Framework, invece, genera il seguente codice SQL:

SELECT 
CASE 
  WHEN (( NOT (([UnionAll1].[C2] = 1) AND ([UnionAll1].[C2] IS NOT NULL))) 
    AND ( NOT (([UnionAll1].[C3] = 1) AND ([UnionAll1].[C3] IS NOT NULL)))) 
    THEN '0X' 
  WHEN (([UnionAll1].[C2] = 1) 
    AND ([UnionAll1].[C2] IS NOT NULL)) 
    THEN '0X0X' 
    ELSE '0X1X' 
END AS [C1], 
[Extent1].[VehicleId] AS [VehicleId], 
[Extent1].[Name] AS [Name], 
CASE 
  WHEN (( NOT (([UnionAll1].[C2] = 1) AND ([UnionAll1].[C2] IS NOT NULL))) 
    AND ( NOT (([UnionAll1].[C3] = 1) AND ([UnionAll1].[C3] IS NOT NULL)))) 
    THEN CAST(NULL AS bit) 
  WHEN (([UnionAll1].[C2] = 1) AND ([UnionAll1].[C2] IS NOT NULL)) 
    THEN [UnionAll1].[AirConditioning] 
END AS [C2], 
CASE 
  WHEN (( NOT (([UnionAll1].[C2] = 1) AND ([UnionAll1].[C2] IS NOT NULL))) 
    AND ( NOT (([UnionAll1].[C3] = 1) AND ([UnionAll1].[C3] IS NOT NULL)))) 
    THEN CAST(NULL AS bit) 
  WHEN (([UnionAll1].[C2] = 1) AND ([UnionAll1].[C2] IS NOT NULL)) 
    THEN [UnionAll1].[Stereo] 
END AS [C3], 
CASE 
  WHEN (( NOT (([UnionAll1].[C2] = 1) AND ([UnionAll1].[C2] IS NOT NULL))) 
    AND ( NOT (([UnionAll1].[C3] = 1) AND ([UnionAll1].[C3] IS NOT NULL)))) 
    THEN CAST(NULL AS varchar(1)) 
  WHEN (([UnionAll1].[C2] = 1) AND ([UnionAll1].[C2] IS NOT NULL)) 
    THEN CAST(NULL AS varchar(1)) 
    ELSE [UnionAll1].[C1] 
END AS [C4]
FROM  [dbo].[Vehicle] AS [Extent1]
LEFT OUTER JOIN  
  (SELECT [Extent2].[VehicleId] AS [VehicleId], 
          [Extent2].[AirConditioning] AS [AirConditioning], 
          [Extent2].[Stereo] AS [Stereo], 
          CAST(NULL AS varchar(1)) AS [C1], 
          cast(1 as bit) AS [C2], 
          cast(0 as bit) AS [C3]
   FROM [dbo].[Car] AS [Extent2]
   UNION ALL
   SELECT [Extent3].[VehicleId] AS [VehicleId], 
          CAST(NULL AS bit) AS [C1], 
          CAST(NULL AS bit) AS [C2], 
          [Extent3].[Bag] AS [Bag], 
          cast(0 as bit) AS [C3], 
          cast(1 as bit) AS [C4]
   FROM [dbo].[Motorcycle] AS [Extent3]) AS [UnionAll1] 
ON [Extent1].[VehicleId] = [UnionAll1].[VehicleId]

Come si vede, anche una query relativamente semplice può generare un codice SQL sproporzionato. La cosa migliore in questi casi è creare una stored procedure ad hoc ed invocare questa invece che sfruttare LINQ to Entities o Entity SQL. In un precedente articolo abbiamo già parlato di come creare ed utilizzare stored procedure. Per scoprire come creare, mappare ed utilizzare le stored procedure con Entity Framework si rimanda alla sua lettura.

3 pagine in totale: 1 2 3
Contenuti dell'articolo

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti