DarioSantarelli.Blog("UgiDotNet");

<sharing mode=”On” users=”*” />
posts - 176, comments - 105, trackbacks - 3

My Links

News


This is my personal blog. These postings are provided "AS IS" with no warranties, and confer no rights.




Tag Cloud

Archives

Post Categories

My English Blog

giovedì 28 agosto 2008

[Entity Framework] Lazy Loading implicito

Quando si parla di Lazy Loading nell' ambito dell' Entity Framework spesso si crea un po' di confusione soprattutto se si fanno dei parallelismi sbagliati con altri ORM. In questo post cercherò quindi di fare un po' di chiarezza a riguardo. 
L'Entity Framework NON supporta di il Lazy Loading così come lo conosciamo in qualità di design pattern. Piuttosto, l'EF fornisce nativamente gli strumenti per configurare e gestire il "deferred loading".  Per questo motivo potremmo dire che, sotto certi punti di vista, nell' EF i meccanismi di lazy loading, lazy initialization, deferred loading, on-demand loading e just-in-time loading praticamente significano la stessa cosa. Affrontiamo questo delicato argomento gradualmente partendo da un semplice modello di esempio basato sul solito DB Northwind:

NorthwindEntities entities = new NorthwindEntities();
foreach (Product product in entities.Products)
{
 
product.CategoryReference.Load();
 
Console.WriteLine(product.Category.CategoryName);
}

In questa scelta implementativa, il caricamento della Category correlata a ciascun Product avviene con un deferred loading esplicito tramite l'invocazione del metodo Load() sull'oggetto (di tipo RelatedEnd )CategoryReference.
N.B.: in assenza di questa istruzione si scatenerebbe una NullReferenceException poiché la "Navigation Property" Category non viene inizializzata automaticamente nel momento in cui vengono estratti i Product a cui si riferiscono. Più precisamente, l'EF di default non controlla il tracking repository per verificare se l'oggetto correlato è stato caricato in memoria o meno. 

Questo comportamento è completamente opposto a quello di LINQ to SQL che invece cerca di rendere più efficiente il recupero dei dati dal DB automatizzando il reperimento delle informazioni in base al tracking degli oggetti in cache. Il motivo di questa differenza è in realtà molto semplice (da MSDN):

When architecting a larger project, it is highly important for developers to clearly understand when they are accessing certain resources, such as the database.

Tornando al nostro esempio, il risultato che otteniamo è facilmente intuibile: ad ogni iterazione viene eseguita una query del tipo...

SELECT[Extent2].[CategoryID] AS [CategoryID],[Extent2].[CategoryName] AS [CategoryName] ...
FROM  [dbo].[Products] AS [Extent1]
INNER JOIN [dbo].[Categories] AS [Extent2] ON [Extent1].[CategoryID] = [Extent2].[CategoryID]
WHERE ([Extent1].[CategoryID] IS NOT NULL) AND ([Extent1].[ProductID] = @EntityKeyValue1)

...per estrarre un Product e la relativa Category. In totale, il numero di query che otteniamo è dunque N + 1 (N = numero dei Products). Possiamo/Dobbiamo fare di meglio.
In casi come questo è infatti l' eager loading a costituire un approccio decisamente più performante, soprattutto quando i Product estratti dalla prima query sono molti. Possiamo quindi modificare l' esempio precedente in questo modo:

NorthwindEntities entities = new NorthwindEntities();
foreach
(Product product in entities.Products.Include("Category"))
{
 
Console.WriteLine(product.Category.CategoryName);
}

Il metodo Include(...) ci permette di specificare uno o più oggetti correlati (separati da '.') da includere appunto nel risultato della query. In questo modo estraiamo tutte le Entity che ci servono grazie ad un'unica query:

SELECT 1 AS [C1], [Extent1].[Discontinued] AS [Discontinued], [Extent1].[ProductID] AS [ProductID], ..., [Extent2].[CategoryID] AS [CategoryID], [Extent2].[CategoryName] AS [CategoryName]...
FROM  [dbo].[Products] AS [Extent1]
LEFT OUTER JOIN [dbo].[Categories] AS [Extent2] ON [Extent1].[CategoryID] = [Extent2].[CategoryID]

E' lampante che il punto critico di questo approccio riguarda le dimensioni della query in termini di JOIN che vengono autogenerati. Infatti, l'eager loading su parecchie Navigation Properties può generare degli statement SQL veramente pesanti che andrebbero gestiti diversamente.

Arriviamo al punto: utilizzando l'EF, i due approcci mostrati possono essere mediati da un buon compromesso che si basa sull' implementare esplicitamente il normale comportamento di LINQ to SQL: il lazy loading implicito che sfrutta il servizio di tracking degli oggetti.
ATTENZIONE: ciò che si vuole realizzare in questa fase NON è un Lazy Load a tutti gli effetti, bensì una sua "emulazione". In questo caso infatti si passa tramite un oggetto intermedio (CategoryReference) che non identifica direttamente la proprietà a cui si vuole accedere. Come asserito all'inizio del post, l'EF non supporta il design pattern Lazy Loading.

NorthwindEntities entities = new NorthwindEntities();
foreach (Product product in entities.Products)
{
 
if (!product.CategoryReference.IsLoaded)
 
{
   
product.CategoryReference.Load();
 
}
 
Console.WriteLine(product.Category.CategoryName);
}

Grazie alla proprietà IsLoaded possiamo stabilire se la Category correlata ad un Product è già stata caricata e dunque siamo in grado di evitare roundtrip ridondanti verso il DB.
Questo semplice "tweak" si rivela particolarmente utile e performante nel momento in cui si prevedono molte Category condivise da almeno due Product dell'insieme in esame.
In soldoni, ciò implica che il numero di query che otteniamo è N + 1 - M (M = numero degli oggetti Product che possiedono la stessa Category di un almeno un altro Product).  Minore è il numero complessivo delle Category coinvolte nell'insieme di Product considerato, minore sarà il numero di query che verranno generate.

Technorati tags:  Entity Framework,  Lazy Loading

posted @ lunedì 1 gennaio 0001 00:00 | Feedback (7) |

Powered by:
Powered By Subtext Powered By ASP.NET