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

[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

Print | posted on giovedì 28 agosto 2008 12:56 |

Feedback

Gravatar

# re: [Entity Framework] Lazy Loading implicito

Molto interessante. A causa di questi comportamenti diversi tral LINQ To SQL ed EF si potrebbero degradare le prestazioni generali senza accorgersene.
28/08/2008 13:34 | Pietro Libro
Gravatar

# re: [Entity Framework] Lazy Loading implicito

La tua precisazione è sacrosanta. All'inizio del post scrivo:
"L'Entity Framework NON supporta 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". "
In realtà ciò che ho intenzione di far passare come messaggio è che nell'EF il lazy load al massimo può essere 'emulato' con un tweak. Ma rimane comunque il fatto che non si tratta di un lazy load a tutti gli effetti :).
Relativamente a questo punto, provvederò a correggere il post prima possibile.

Un saluto
28/08/2008 14:59 | Dario Santarelli
Gravatar

# Re: [Entity Framework] Lazy Loading implicito

Dario, non volevo essere ploemico, semplicemente spesso le parole rimangono nella testa delle persone, quindi volevo solo cercare di evitare se possibile il termine "lazy loading implicito", perchè seppur spiegato correttamente e accompagnato dal termine "implicito", rimane "lazy load" e inevitabilmente a chi legge rimarrà l'impressione che EF in qualche modo lo supporti.
Personalmente direi che EF supporta la specificazione del fetch paln: eager tramite la clausola Include degli EntitySet (che sono degli ObjectQuery), o deferred tramite il metodo Load di EnityReeference o EntityCollection (accompagnato dalla proprietà IsLoaded).
poi direi chiaramente che no, EF non supporta in alcun modo il lazy load, e anzi, sarei categorico in questo.

saluti
Roberto
Gravatar

# Re: [Entity Framework] Lazy Loading implicito

comunque, bel post!
28/08/2008 17:33 | Igor Damiani
Gravatar

# re: [Entity Framework] Lazy Loading implicito

Ottimo post...complimenti...
31/08/2008 12:45 | Luca
Gravatar

# re: [Entity Framework] Lazy Loading implicito

Post molto utile x chi non ha molta dimestichezza con EF.
Discussione a seguire da veri puristi...

Interessante
29/06/2010 18:30 | Giuseppe
Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET