If you have ever written a Delphi application that talks to a database, you know the routine: write SQL by hand, manage parameters, loop through datasets, and copy values into objects field by field. It works, but it is tedious and error-prone.
Trysil is an open-source ORM for Delphi that eliminates that boilerplate. You decorate your classes with attributes, and the framework handles the rest — mapping, querying, inserting, updating, deleting. It is lightweight, attribute-driven, and built on top of FireDAC, so it works with SQLite, PostgreSQL, SQL Server, and Firebird out of the box.
In this first article we will go from zero to a working CRUD application with SQLite.
Installation
You can install Trysil in three ways:
- GetIt — search for "Trysil" in the Embarcadero GetIt Package Manager
-
Boss —
boss install davidlastrucci/Trysil -
Manual — clone the GitHub repo, open
Packages/<ver>/Trysil.groupproj, and build all
After installation, point your project's Search Path to the compiled output directory.
Your first entity
An entity in Trysil is a plain Delphi class with attributes. No base class to inherit, no interface to implement.
unit Contact.Model;
{$WARN UNKNOWN_CUSTOM_ATTRIBUTE ERROR}
interface
uses
Trysil.Types,
Trysil.Attributes,
Trysil.Validation.Attributes;
type
[TTable('Contacts')]
[TSequence('ContactsID')]
TTContact = class
strict private
[TPrimaryKey]
[TColumn('ID')]
FID: TTPrimaryKey;
[TRequired]
[TMaxLength(50)]
[TColumn('Firstname')]
FFirstname: String;
[TRequired]
[TMaxLength(50)]
[TColumn('Lastname')]
FLastname: String;
[TMaxLength(255)]
[TEmail]
[TColumn('Email')]
FEmail: String;
[TColumn('VersionID')]
[TVersionColumn]
FVersionID: TTVersion;
public
property ID: TTPrimaryKey read FID;
property Firstname: String read FFirstname write FFirstname;
property Lastname: String read FLastname write FLastname;
property Email: String read FEmail write FEmail;
property VersionID: TTVersion read FVersionID;
end;
A few things to note:
-
[TTable('Contacts')]maps the class to theContactstable. -
[TSequence('ContactsID')]tells Trysil how the primary key is generated. -
[TPrimaryKey]and[TColumn('...')]mark the primary key and map each field to a column. -
[TVersionColumn]enables optimistic locking — Trysil will check this value on every update to prevent lost writes. -
[TRequired],[TMaxLength],[TEmail]are validation attributes — we will cover them in depth in a later article. -
{$WARN UNKNOWN_CUSTOM_ATTRIBUTE ERROR}turns typos in attribute names into compile-time errors. Always add this.
Fields are strict private with public properties. The ORM accesses them through RTTI.
Connecting to SQLite
uses
Trysil.Data,
Trysil.Data.FireDAC.SQLite,
Trysil.Data.FireDAC.ConnectionPool,
Trysil.Context;
var
LConnection: TTConnection;
LContext: TTContext;
begin
// Disable connection pooling (not needed for desktop apps)
TTFireDACConnectionPool.Instance.Config.Enabled := False;
// Register and create the connection
TTSQLiteConnection.RegisterConnection('MyApp', 'contacts.db');
LConnection := TTSQLiteConnection.Create('MyApp');
try
LContext := TTContext.Create(LConnection);
try
// ... use the context
finally
LContext.Free;
end;
finally
LConnection.Free;
end;
end;
TTContext is the single entry point for all ORM operations. You create it with a connection, and it gives you methods for reading, writing, and querying entities.
CRUD operations
Create
var
LContact: TTContact;
begin
LContact := LContext.CreateEntity<TTContact>();
LContact.Firstname := 'Ada';
LContact.Lastname := 'Lovelace';
LContact.Email := 'ada@example.com';
LContext.Insert<TTContact>(LContact);
end;
Always use CreateEntity<T> to create new entities — it initializes internal state that the context needs to track the object.
Read
var
LContacts: TTList<TTContact>;
begin
LContacts := TTList<TTContact>.Create;
try
// Load all contacts
LContext.SelectAll<TTContact>(LContacts);
for LContact in LContacts do
WriteLn(Format('%s %s — %s', [
LContact.Firstname,
LContact.Lastname,
LContact.Email]));
finally
LContacts.Free;
end;
end;
To fetch a single entity by ID:
var
LContact: TTContact;
begin
LContact := LContext.Get<TTContact>(42);
// raises an exception if not found
end;
Update
LContact.Email := 'ada.lovelace@example.com';
LContext.Update<TTContact>(LContact);
Behind the scenes, Trysil generates an UPDATE statement that includes the VersionID in the WHERE clause. If another user has modified the same record in the meantime, the update fails with a concurrency error rather than silently overwriting their changes.
Delete
LContext.Delete<TTContact>(LContact);
The SQL table
For completeness, here is the SQLite schema that matches our entity:
CREATE TABLE Contacts (
ID INTEGER PRIMARY KEY,
Firstname TEXT NOT NULL,
Lastname TEXT NOT NULL,
Email TEXT,
VersionID INTEGER NOT NULL DEFAULT 1
);
What is next
In this article we defined an entity, connected to SQLite, and performed full CRUD — all without writing a single line of SQL.
In the next article we will look deeper into entity mapping: nullable fields, custom types, and how Trysil handles the relationship between your Delphi classes and your database schema.
The full source code is available on GitHub.
Trysil is an open-source project. If you find it useful, consider giving it a star on GitHub!
Top comments (0)