Când vine vorba de a accesa o baza de date in .NET, in particular folosind un O/R mapper, multă lume a auzit de "lazy loading" - dându-se un obiect "rădăcina", obiectele sau colecţiile care le referenţiază vor fi încărcate din baza de date doar la prima accesare, nu odată cu acel obiect. O astfel de soluţie are şi avantaje - datele sunt încărcate doar când e nevoie de ele, şi acest proces e transparent pentru programator, dar şi dezavantaje - aplicaţia va face numeroase apeluri separate la baza de date, fiecare cu un overhead in plus.
Alternantiva? "Eager loading" - altfel spus, dacă ştim că, într-un anumit "context" (indiferent că e un unit-of-work, comandă, metodă, form/view etc. - depinde de unde privim) vom avea nevoie cu mare probabilitate de un anumit set de obiecte (pentru a le afişa, edita, prelucra etc.), poate e mai bine sa îi spună O/R mapper-ului (sau in general DAL-ului) în avans de ce obiecte vom avea nevoie sa fie încărcate din baza de date.
Avantaje? Încărcăm toate datele necesare cu un singur drum la baza de date, lăsând O/RM-ul sa optimizeze modul de acces. Dezavantaje? Riscăm să încărcăm mai multe date decât vom avea nevoie, şi să încărcăm memoria cu obiecte care nu vor fi folosite decât mai târziu.
Cum s-ar putea face asta? În SQL, "specificăm" ce date vrem să fie încărcate folosind unul sau mai multe SELECT-uri (care probabil vor include joins, proiecţii, unions etc..). În lumea claselor şi a obiectelor, nu mai avem o lume "tabelara", ci o mulţime de clase (unii prefera sa le numească entităţi), deseori legate între ele prin asocieri (1:n, n:1, m:n..). Concret, aceste asocieri se materializează într-o clasă prin property-uri de tip referinţă (la alte clase sau colecţii de obiecte). Indiferent de modul cum implementam asta, conceptual clasele dintr-o aplicaţie se poate spune că formează un graf (uite că e bună şi teoria aia din facultate la ceva..). Dacă pornim de la o clasa anume (nod), cel mai adesea putem specifica restul obiectelor de care vom avea nevoie sub forma unui sub-graf (finit) ce porneşte de la acea clasă.
Ok, gata cu teoria. Să iau un exemplu concret: să zicem că intr-o aplicaţie (web sau desktop, nu contează), trebuie sa afişăm (poate să şi editam) un client (Person class), împreună cu datele firmei (Company) care o reprezintă, lista comenzilor (Orders) făcute de acel client, şi pentru fiecare comanda, câteva detalii despre produsul comandat (Product) - presupunem că un order corespunde unui singur produs (in aplicaţii reale cel mai adesea avem ceva gen order / order lines / product). Avem deci un caz destul de clasic şi banal, care intr-un class diagram arata cam aşa:

(exemplu facut la repezeala, nu ma impuscati daca nu e UML ca la carte, si nici daca nu e modelat cum trebuie :)
Am inclus în diagramă şi câteva clase in plus - in orice aplicate reala graful format de diverse clase e mult mai stufos.
Ca să complicăm puţin problema, să spunem că doar comenzile care nu au fost livrate (IsDelivered) încă trebuie afişate (sau editate) - normal, intr-o aplicaţie reala treaba asta nu e modelata doar printr-un singur flag, dar pentru un exemplu e ok. In final, din toate cele "20" de atribute care le poate avea un produs, să zicem că nu trebuie afişate decât câteva esenţiale: Name (string), Color (string), Barcode (string) - suficiente pentru a identifică un produs.
Ajungând in acest punct, întrebarea e - cum putem descrie (intr-o structura de date sau altfel) acest "sub-graf". Din păcate, la acest capitol, deşi asemănătoare, fiecare O/RM are alta soluţie.
Dacă la problema "cum descriem o interogare/filtru", in lumea .NET soluţia a apărut in cele din urma (LINQ), şi majoritatea O/RM-urilor oferă suport pentru LINQ mai mult sau mai puţin, când vine vorba de "descrierea grafului de obiecte ce va fi pre-incărcat", nu exista încă o metoda unitara (sau nu ştiu eu să existe).
Voi lua ca exemplu 3 framework-uri dintre cele mai cunoscute: ADO.NET Entity Framework (Microsoft), LLBLGen Pro (Solution Design bv, Hague, Olanda) şi NHibernate (open source, LGPL).
In LLBLGen Pro (v. 2.6), sub-graful care se doreşte să fie pre-incărcat e descris printr-un aşa-numit "prefetch path", şi in cazul nostru va arata cam aşa:
///////////////////////////////////
// clasa de la care pornim, nodul "rădăcina"
IPrefetchPath2 prefetchPath = new PrefetchPath2((int)EntityType.PersonEntity);
prefetchPath.Add(PersonEntity.PrefetchPathCompany); // încarcă company
// vrem doar acele orders care nu au fost livrate încă
IPredicateExpression ordersPredicate = (OrderFields.IsDelivered == false); // operator overloading
// specificăm ce property-uri din clasa Product for fi încărcate
// (doar field-urile corespondente vor fi incluse in clauza SELECT generata)
IncludeFieldsList productIncludedFields = new IncludeFieldsList();
productIncludedFields.Add(ProductFields.Name);
productIncludedFields.Add(ProductFields.Color);
productIncludedFields.Add(ProductFields.Barcode);
// "încarcă toate elementele din colecţia Purchases, care satisfac predicatul"
// 0 - all, >0 - TOP n ...
prefetchPath.Add(PersonEntity.PrefetchPathPurchases, 0, ordersPredicate)
.SubPath.Add(OrderEntity.PrefetchPathProduct, 0, null, null, null, null,
productIncludedFields);
// o grămadă de parametrii opţionali, toţi la valoarea default :)
// încărcăm obiectul customer, de tip PersonEntity, din baza de date
PersonEntity customer = new PersonEntity(personId);
using (DataAccessAdapter adapter = new DataAccessAdapter())
{
adapter.FetchEntity(customer, prefetchPath);
}
///////////////////////////////
Destul de complicat, dar destul de logic.
Pentru cine se simte mai acasa in lumea LINQ si a lambda-expressions, incepand cu versiunea 2.6, LLBLGen permite speciifcarea unui prefetch sub o forma alternativa, cand e vorba de query-uri LINQ.
Ce va genera LLBLGen-ul din cârnaţul de cod de mai sus? - logic, fie o succesiune de SELECT-uri, fie niste JOIN-uri, după cum au crezut că e mai optim.
Cum LLBLGen nu e doar un O/RM ci şi un generator de cod (la fel ca Entity Framework), modul in care e specificat eager loading-ul e strongly typed (deci fara a folosi string-uri hard-codate, ci folosind diverse elemente ce descriu metadatele: PersonEntity, PrefetchPathCompany, ProductFields etc.), toate generate odată cu clasele ce compun domain model-ul.
Pe buna dreptate, cineva s-ar putea întreba: pentru a descrie calea de la Person pana la Product, nu era suficient ceva de genul: Person -> Order -> Product?
Din păcate, in general nu e suficient: de ex., intre clasa Person şi clasa Order, exista doua relaţii de asociere: un Person poate fi clientul care a făcut comanda, dar poate fi şi angajatul care a preluat comanda. In termenii bazelor de date: intr-o tabela (orders) pot exista doua foreign-keys diferite către alta tabela (Person) - dacă pornim de la Person, care din cele doua relaţii o vom folosi pentru a încărca colecţia de Orders ?
Tocmai din acest motiv, un "prefetch path" in LLBLGen e de forma: [EntityName].[Property/Collection name]Prefetch.
Mai multe detalii, la: [http://www.llblgen.com/documentation/2.6/Using%20the%20generated%20code/Adapter/gencode_prefetchpaths_adapter.htm]
In "episodul" următor, vom arunca o privire la modul in care se realizează eager loading in ADO.NET Entity Framework şi NHibernate.