Blog

All Blog Posts  |  Next Post  |  Previous Post

My Top 10 Aurelius Features - #8 Lazy Loading

Thursday, December 22, 2016

The ability to lazy-load an association is placed at number 8 in the list of My Top 10 Aurelius Features.


Suppose you have a TContact class like the following:
type
  TContact = class
  private
    FId: integer;
    FName: string;
    FCountry: TCountry;
  public
    property Id: integer read FId write FId;
    property Name: string read FName write FName;
    property Country: TCountry read FCountry write FCountry;
  end;
And you use the following code to retrieve a list of contacts and get the name of the country of the first contact (to reduce size, the code doesn't check for potential errors or release memory):
// Get all contacts
MyContacts := Manager.Find<TContact>.List;

// Get name of country of first contact:
FirstContactCountryName := MyContacts[0].Country.Name;
By default, when the first line is executed, Aurelius builds and executes an SQL statement retrieving column values from Contact table and Country table, all at once, and all entities (contacts and countries) are created and instantiated:
SELECT A.ID AS A_ID, A.NAME AS A_NAME, A.COUNTRY_ID AS A_COUNTRY_ID, B.ID AS B_ID, B.NAME AS B_NAME
FROM CONTACT A LEFT JOIN COUNTRY B ON (B.ID = A.COUNTRY_ID)
When the second line is executed (when we retrieve the Country name), all data is already in memory. This is useful in many situations, but there might be cases where doing several LEFT JOIN for many associations and retrieving data for all records is not desired. If I'm not going to get the country name for all contacts I retrieve, for example, why would I want to retrieve all country names in advance?

The lazy-loading feature gives you that flexibility. You just reimplement your class using a special Proxy type:
type
  TContact = class
  private
    FId: integer;
    FName: string;
    FCountry: Proxy<TCountry>;
    function GetCountry: TCountry;
    procedure SetCountry(const Value: TCountry);
  public
    property Id: integer read FId write FId;
    property Name: string read FName write FName;
    property Country: TCountry read GetCountry write SetCountry;
  end;

function TContact.GetCountry: TCountry;
begin
  Result := FCountry.Value;
end;

procedure TContact.SetCountry(const Value: TCountry);
begin
  FCountry.Value := Value;
end;
If we execute the code that retrieve contacts again, this is the SQL executed by Aurelius:
SELECT A.ID AS A_ID, A.NAME AS A_NAME, A.COUNTRY_ID AS A_COUNTRY_ID 
FROM CONTACT A
No data from country was retrieved. Only when the second line is executed, Aurelius executes an extra SQL statement:
SELECT A.ID AS A_ID, A.NAME AS A_NAME
FROM COUNTRY A
and then the country object is populated. Now you have the two options and you can fine tune your application for the best situation. It's also worth note that even when you have mapped your class to lazy-load a specific association, in some queries you can force Aurelius to still load all objects at once. The following code will use LEFT JOIN and retrieve all countries in the same SQL statement:
// Get all contacts and countries at once,
// regardless of lazy-load mode in mapping
MyContacts := Manager.Find<TContact>
  .CreateAlias('Country', 'c', TFetchMode.Eager)
  .List;

// Get name of country of first contact:
FirstContactCountryName := MyContacts[0].Country.Name;
Don't forget to subscribe to our YouTube channel and receive notifications about upcoming videos!


Wagner Landgraf




This blog post has received 3 comments.


1. Friday, December 23, 2016 at 10:19:55 PM

Something I really would be interested. Assume we have 10 TContact objects and need to lazy load the Country data.
This would result in 10 distinct SQL calls, one for each country data. Coding something like that by hand I could end up with just one SQL with some additional code and optimize performance a lot.
Is there something like that available in the Aurelius framework?
Happy XMas!
Stefan

Stefan


2. Saturday, December 24, 2016 at 12:19:32 PM

Isn''t the answer to your question what is explained in the post/video? You can use CreateAlias with TFetchMode.Eager to "turn" the lazy mode into eager mode and have a single SQL to retrieve them all.

Wagner R. Landgraf


3. Friday, September 4, 2020 at 8:06:16 PM

Thaths perfect!

My questions about object models and ORM''s alyways are about it.

How to optimize SQL when i don''t need retrive all fields from relationship?

The lazy-loading and CreateAlias resolve.

I''m analysing TMS Aurelius and i''m loving it.

Maicon Saraiva




Add a new comment

You will receive a confirmation mail with a link to validate your comment, please use a valid email address.
All fields are required.



All Blog Posts  |  Next Post  |  Previous Post