Lazy Loading¶
Trysil supports deferred loading of related entities through TTLazy<T> and TTLazyList<T>, both defined in Trysil.Lazy.pas. Related data is loaded from the database only when first accessed, avoiding unnecessary queries for relations that may never be used.
TTLazy\<T> (Single Entity)¶
Use TTLazy<T> for a many-to-one relationship where a field references a single related entity.
Entity Definition¶
[TTable('Employees')]
[TSequence('EmployeesID')]
TEmployee = class
strict private
[TPrimaryKey]
[TColumn('ID')]
FID: TTPrimaryKey;
[TColumn('CompanyID')]
FCompany: TTLazy<TCompany>;
[TVersionColumn]
[TColumn('VersionID')]
FVersionID: TTVersion;
function GetCompany: TCompany;
procedure SetCompany(const AValue: TCompany);
public
property ID: TTPrimaryKey read FID;
property Company: TCompany read GetCompany write SetCompany;
end;
Getter and Setter¶
function TEmployee.GetCompany: TCompany;
begin
Result := FCompany.Entity;
end;
procedure TEmployee.SetCompany(const AValue: TCompany);
begin
FCompany.Entity := AValue;
end;
Behavior¶
- The related entity is loaded on the first access to
.Entity. Subsequent accesses return the cached instance. - Setting
.Entityto a different object updates the internal foreign key ID and frees the previous entity (unless the identity map is active). - When the
IDproperty on the lazy field changes, the cached entity is cleared and will be reloaded on next access. - Soft-deleted parents resolve. The lazy load uses
Get<T>(ID, True), so a parent that has been soft-deleted still resolves through its foreign key — a child referencing a logically deleted master is not left with a danglingnilreference.
TTLazyList\<T> (Collection)¶
Use TTLazyList<T> for a one-to-many relationship where a parent entity has multiple children.
Entity Definition¶
[TTable('Departments')]
[TSequence('DepartmentsID')]
TDepartment = class
strict private
[TPrimaryKey]
[TColumn('ID')]
FID: TTPrimaryKey;
[TColumn('DepartmentID')]
FEmployees: TTLazyList<TEmployee>;
function GetEmployees: TTList<TEmployee>;
public
property ID: TTPrimaryKey read FID;
property Employees: TTList<TEmployee> read GetEmployees;
end;
Getter¶
Behavior¶
- The list is loaded on first access. A SELECT query is executed with a filter matching the foreign key column to the parent's ID.
AddEntitycreates a new entity via the context and adds it to the list:
- When the parent's ID changes, the cached list is invalidated and will be reloaded on next access.
Invalidation and Sessions¶
The cached list carries an IsValid flag. When it is cleared -- for example when the parent's ID changes -- the next access re-runs the SELECT and refills the same list instance in place.
This integrates with the Unit of Work: the collection is a TTList<T>, so you pass it straight to CreateSession<T>. When the session's ApplyChanges completes, it invalidates the underlying lazy list automatically, so the in-memory collection reflects the persisted state on the next read:
var LSession := LContext.CreateSession<TEmployee>(LDepartment.Employees);
try
LSession.Entities[0].Lastname := 'Updated';
LSession.Update(LSession.Entities[0]);
LSession.ApplyChanges; // LDepartment.Employees is invalidated here
finally
LSession.Free;
end;
Important Notes¶
-
Context lifetime -- Both
TTLazy<T>andTTLazyList<T>hold a reference to the owningTTContext. The context must remain alive for as long as lazy fields may be accessed. Accessing a lazy field after the context is freed will cause an access violation. -
Identity map interaction -- When
UseIdentityMapisTrue, lazy-loaded entities are not freed by the lazy wrapper (the identity map owns them). WhenUseIdentityMapisFalse, the lazy wrapper owns and frees loaded entities on destruction or when the ID changes. -
Used with TRelation -- Lazy loading is typically combined with the
TRelationattribute on the parent entity class to declare the referential integrity constraint:
- N+1 query pattern -- Be aware that accessing lazy fields in a loop generates one query per entity. For bulk operations, consider loading related data upfront with a filtered
Select<T>call.