Events¶
Trysil provides lifecycle events that fire during Insert, Update and Delete operations (Undelete fires the update events). Events are defined across several units: Trysil.Events.Abstract.pas, Trysil.Events.pas, Trysil.Events.Attributes.pas, and Trysil.Events.Factory.pas.
Lifecycle Events¶
| Event | When |
|---|---|
| BeforeInsert | Before inserting a new record |
| AfterInsert | After inserting a new record |
| BeforeUpdate | Before updating an existing record |
| AfterUpdate | After updating an existing record |
| BeforeDelete | Before deleting a record |
| AfterDelete | After deleting a record |
Event Classes¶
Define custom event logic by extending TTEvent<T>:
type
TPersonInsertEvent = class(TTEvent<TPerson>)
public
procedure DoBefore; override;
procedure DoAfter; override;
end;
procedure TPersonInsertEvent.DoBefore;
begin
// Runs before the INSERT command is executed
if Entity.Firstname = '' then
raise ETException.Create('Firstname is required');
end;
procedure TPersonInsertEvent.DoAfter;
begin
// Runs after the INSERT command has executed
// Entity.ID is now assigned
end;
Available Properties¶
Inside an event class, the following properties are available via TTEvent<T>:
| Property | Type | Description |
|---|---|---|
Entity |
T |
The entity being processed |
OldEntity |
T |
The entity state before changes (loaded lazily from the database on first access) |
Context |
TTContext |
The current context, allowing additional queries or operations |
OldEntity is loaded on demand by calling TTContext.OldEntity<T> internally. It is most useful in update events to compare old and new values. The clone belongs to the event, which frees it when the event is destroyed: do not free it, and do not hand it to a lazy member or keep it past the event. See who frees what.
Registering Event Classes¶
Use event attributes on the entity class declaration:
[TTable('Persons')]
[TSequence('PersonsID')]
[TInsertEvent(TPersonInsertEvent)]
[TUpdateEvent(TPersonUpdateEvent)]
[TDeleteEvent(TPersonDeleteEvent)]
TPerson = class
| Attribute | Triggers |
|---|---|
TInsertEvent(TEventClass) |
DoBefore and DoAfter around INSERT |
TUpdateEvent(TEventClass) |
DoBefore and DoAfter around UPDATE |
TDeleteEvent(TEventClass) |
DoBefore and DoAfter around DELETE |
Each attribute receives a class reference that must descend from TTEvent.
Event Method Attributes¶
For simpler cases where a full event class is not needed, add event methods directly on the entity using method attributes:
TPerson = class
strict private
// fields...
public
[TBeforeInsertEvent]
procedure BeforeInsert;
[TAfterInsertEvent]
procedure AfterInsert;
[TBeforeUpdateEvent]
procedure BeforeUpdate;
[TAfterUpdateEvent]
procedure AfterUpdate;
[TBeforeDeleteEvent]
procedure BeforeDelete;
[TAfterDeleteEvent]
procedure AfterDelete;
end;
Available Method Attributes¶
| Attribute | When |
|---|---|
TBeforeInsertEvent |
Before INSERT |
TAfterInsertEvent |
After INSERT |
TBeforeUpdateEvent |
Before UPDATE |
TAfterUpdateEvent |
After UPDATE |
TBeforeDeleteEvent |
Before DELETE |
TAfterDeleteEvent |
After DELETE |
These methods are invoked by the resolver via RTTI. They must be declared on the entity class itself (not on a parent class), must take no parameters, and must be public.
A private event method is never called
Delphi emits RTTI for public and published methods only. An event
attribute on a private, strict private or protected method is
therefore never seen by the resolver: nothing is registered, nothing
raises, and the method is simply never called. The mistake is silent, so
put event methods in the public section.
Event Execution Order¶
When the resolver processes a write operation, the full sequence is:
- Validation (attribute-based validation, on
Insert,UpdateandUndelete:Deletedoes not validate;Undeletefires the update events) - Event class
DoBefore(if aTInsertEvent/TUpdateEvent/TDeleteEventis registered) - Event method
[TBeforeInsertEvent]/[TBeforeUpdateEvent]/[TBeforeDeleteEvent]on the entity - SQL command execution (INSERT / UPDATE / DELETE)
- Event class
DoAfter - Event method
[TAfterInsertEvent]/[TAfterUpdateEvent]/[TAfterDeleteEvent]on the entity
Raising Exceptions in Events¶
Raising an exception in a DoBefore method or a [TBeforeInsertEvent] method prevents the SQL command from executing. If a transaction is active, the exception propagates and can trigger a rollback:
procedure TOrderDeleteEvent.DoBefore;
begin
if Entity.Status = 'Shipped' then
raise ETException.Create('Cannot delete a shipped order');
end;
Example: Audit Logging¶
type
TPersonUpdateEvent = class(TTEvent<TPerson>)
public
procedure DoAfter; override;
end;
procedure TPersonUpdateEvent.DoAfter;
var
LAudit: TAuditLog;
begin
LAudit := Context.CreateEntity<TAuditLog>();
try
LAudit.TableName := 'Persons';
LAudit.EntityID := Entity.ID;
LAudit.Action := 'UPDATE';
LAudit.Timestamp := Now;
Context.Insert<TAuditLog>(LAudit);
finally
Context.FreeEntity<TAuditLog>(LAudit);
end;
end;
The audit entry is freed with FreeEntity<T>, not with Free. The event runs
inside the transaction Trysil opened for the update, and if that transaction
rolls back it puts back what it wrote to LAudit too: FreeEntity<T> tells it
to forget the entity first, a bare Free leaves it writing into freed memory.