<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: David Lastrucci</title>
    <description>The latest articles on Forem by David Lastrucci (@davidlastrucci).</description>
    <link>https://forem.com/davidlastrucci</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F903390%2F6c4c9e75-49da-4a91-b381-bd1cc7de9781.jpg</url>
      <title>Forem: David Lastrucci</title>
      <link>https://forem.com/davidlastrucci</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/davidlastrucci"/>
    <language>en</language>
    <item>
      <title>Entity mapping in depth: attributes, types, and nullable fields</title>
      <dc:creator>David Lastrucci</dc:creator>
      <pubDate>Sun, 12 Apr 2026 12:27:02 +0000</pubDate>
      <link>https://forem.com/davidlastrucci/entity-mapping-in-depth-attributes-types-and-nullable-fields-3lo</link>
      <guid>https://forem.com/davidlastrucci/entity-mapping-in-depth-attributes-types-and-nullable-fields-3lo</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/davidlastrucci/meet-trysil-a-lightweight-orm-for-delphi-45c2"&gt;previous article&lt;/a&gt; we created a simple entity and performed CRUD operations. Now let's look at how entity mapping actually works under the hood, and how to handle more realistic scenarios like nullable fields and optimistic locking.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mapping attributes
&lt;/h2&gt;

&lt;p&gt;Every Trysil entity needs at least two class-level attributes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[TTable('Contacts')]      // which table
[TSequence('ContactsID')]   // how the PK is generated
TTContact = class
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And every mapped field needs a &lt;code&gt;[TColumn]&lt;/code&gt; attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[TColumn('Firstname')]
FFirstname: String;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the full set of mapping attributes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attribute&lt;/th&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TTable&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Class&lt;/td&gt;
&lt;td&gt;Maps the class to a database table&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TSequence&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Class&lt;/td&gt;
&lt;td&gt;Names the sequence for the PK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TPrimaryKey&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Field&lt;/td&gt;
&lt;td&gt;Marks the primary key field&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TColumn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Field&lt;/td&gt;
&lt;td&gt;Maps a field to a column&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TVersionColumn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Field&lt;/td&gt;
&lt;td&gt;Enables optimistic locking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;TRelation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Class&lt;/td&gt;
&lt;td&gt;Declares a parent-child relationship (covered in a later article)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Core types
&lt;/h2&gt;

&lt;p&gt;Trysil defines three foundational types in &lt;code&gt;Trysil.Types&lt;/code&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  TTPrimaryKey
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[TPrimaryKey]
[TColumn('ID')]
FID: TTPrimaryKey;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;TTPrimaryKey&lt;/code&gt; is an alias for &lt;code&gt;Int32&lt;/code&gt;. Every entity must have exactly one primary key field of this type. The value is auto-generated by the database.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ID&lt;/code&gt; property is read-only — you never set it manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;property ID: TTPrimaryKey read FID;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  TTVersion
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[TColumn('VersionID')]
[TVersionColumn]
FVersionID: TTVersion;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;TTVersion&lt;/code&gt; is also an &lt;code&gt;Int32&lt;/code&gt;. When you mark a field with &lt;code&gt;[TVersionColumn]&lt;/code&gt;, Trysil includes it in the WHERE clause of UPDATE and DELETE statements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;Contacts&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;VersionID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;VersionID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the version in the database does not match the version in your object, the update affects zero rows and Trysil raises an exception. This is &lt;strong&gt;optimistic locking&lt;/strong&gt; — it prevents two users from silently overwriting each other's changes.&lt;/p&gt;

&lt;p&gt;After a successful update, Trysil increments the version automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  TTNullable&amp;lt;T&amp;gt;
&lt;/h3&gt;

&lt;p&gt;Database columns are often nullable, but Delphi value types (&lt;code&gt;Integer&lt;/code&gt;, &lt;code&gt;TDateTime&lt;/code&gt;, &lt;code&gt;Double&lt;/code&gt;) cannot be null. Trysil solves this with a generic record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[TColumn('BirthDate')]
FBirthDate: TTNullable&amp;lt;TDateTime&amp;gt;;

[TColumn('Score')]
FScore: TTNullable&amp;lt;Integer&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;TTNullable&amp;lt;T&amp;gt;&lt;/code&gt; has no default constructor. When you declare a field of this type and do not assign a value, it is null:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var
  LDate: TTNullable&amp;lt;TDateTime&amp;gt;;
begin
  // LDate is null here

  if LDate.IsNull then
    WriteLn('No date set');

  // Assign a value
  LDate := TTNullable&amp;lt;TDateTime&amp;gt;.Create(Now);
  WriteLn(DateTimeToStr(LDate.Value));
end;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the database, a null &lt;code&gt;TTNullable&amp;lt;T&amp;gt;&lt;/code&gt; field is stored as SQL &lt;code&gt;NULL&lt;/code&gt;. When reading, SQL &lt;code&gt;NULL&lt;/code&gt; maps back to the null state.&lt;/p&gt;

&lt;h2&gt;
  
  
  A more realistic entity
&lt;/h2&gt;

&lt;p&gt;Let's build a &lt;code&gt;TTProduct&lt;/code&gt; entity that uses all of these types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unit Product.Model;

{$WARN UNKNOWN_CUSTOM_ATTRIBUTE ERROR}

interface

uses
  System.SysUtils,
  Trysil.Types,
  Trysil.Attributes,
  Trysil.Validation.Attributes;

type
  [TTable('Products')]
  [TSequence('ProductsID')]
  TTProduct = class
  strict private
    [TPrimaryKey]
    [TColumn('ID')]
    FID: TTPrimaryKey;

    [TRequired]
    [TMaxLength(100)]
    [TColumn('Description')]
    FDescription: String;

    [TGreater(0.00)]
    [TColumn('Price')]
    FPrice: Double;

    [TColumn('DiscountedPrice')]
    FDiscountedPrice: TTNullable&amp;lt;Double&amp;gt;;

    [TColumn('AvailableFrom')]
    FAvailableFrom: TTNullable&amp;lt;TDateTime&amp;gt;;

    [TColumn('VersionID')]
    [TVersionColumn]
    FVersionID: TTVersion;
  public
    property ID: TTPrimaryKey read FID;
    property Description: String read FDescription write FDescription;
    property Price: Double read FPrice write FPrice;
    property DiscountedPrice: TTNullable&amp;lt;Double&amp;gt;
      read FDiscountedPrice write FDiscountedPrice;
    property AvailableFrom: TTNullable&amp;lt;TDateTime&amp;gt;
      read FAvailableFrom write FAvailableFrom;
    property VersionID: TTVersion read FVersionID;
  end;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The matching SQL table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Products&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;Description&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;Price&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;DiscountedPrice&lt;/span&gt; &lt;span class="nb"&gt;REAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;AvailableFrom&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;VersionID&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note how &lt;code&gt;DiscountedPrice&lt;/code&gt; and &lt;code&gt;AvailableFrom&lt;/code&gt; are nullable in both the Delphi entity and the SQL schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with nullable fields
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var
  LProduct: TTProduct;
begin
  LProduct := LContext.CreateEntity&amp;lt;TTProduct&amp;gt;();
  LProduct.Description := 'Mechanical keyboard';
  LProduct.Price := 149.99;
  // DiscountedPrice and AvailableFrom are null — we simply don't set them

  LContext.Insert&amp;lt;TTProduct&amp;gt;(LProduct);

  // Later, set a discounted price
  LProduct.DiscountedPrice := TTNullable&amp;lt;Double&amp;gt;.Create(119.99);
  LContext.Update&amp;lt;TTProduct&amp;gt;(LProduct);
end;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How the mapping cache works
&lt;/h2&gt;

&lt;p&gt;The first time you use an entity type with &lt;code&gt;TTContext&lt;/code&gt;, Trysil reads its attributes via RTTI and builds a &lt;code&gt;TTTableMap&lt;/code&gt; — an internal representation of the table name, columns, primary key, version column, and relationships.&lt;/p&gt;

&lt;p&gt;This mapping is cached globally in &lt;strong&gt;&lt;code&gt;TTMapper.Instance&lt;/code&gt;&lt;/strong&gt; (a singleton). Subsequent operations on the same entity type skip the RTTI scan entirely. This means the reflection cost is paid only once per entity type for the lifetime of your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compile-time safety
&lt;/h2&gt;

&lt;p&gt;Always add this directive at the top of your model units:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{$WARN UNKNOWN_CUSTOM_ATTRIBUTE ERROR}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without it, a typo like &lt;code&gt;[TColum('Name')]&lt;/code&gt; compiles silently and the field is never mapped. With the directive, the compiler flags it as an error immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is next
&lt;/h2&gt;

&lt;p&gt;We now know how to map classes to tables, handle nullable fields, and rely on optimistic locking for safe concurrent updates. In the next article we will explore &lt;strong&gt;validation&lt;/strong&gt; — how Trysil checks your data before it hits the database.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Trysil is open-source and available on &lt;a href="https://github.com/davidlastrucci/Trysil" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Feedback and contributions are welcome!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>delphi</category>
      <category>orm</category>
      <category>database</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Meet Trysil: a lightweight ORM for Delphi</title>
      <dc:creator>David Lastrucci</dc:creator>
      <pubDate>Thu, 09 Apr 2026 20:21:17 +0000</pubDate>
      <link>https://forem.com/davidlastrucci/meet-trysil-a-lightweight-orm-for-delphi-45c2</link>
      <guid>https://forem.com/davidlastrucci/meet-trysil-a-lightweight-orm-for-delphi-45c2</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trysil&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;In this first article we will go from zero to a working CRUD application with SQLite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;You can install Trysil in three ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GetIt&lt;/strong&gt; — search for "Trysil" in the Embarcadero GetIt Package Manager&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boss&lt;/strong&gt; — &lt;code&gt;boss install davidlastrucci/Trysil&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual&lt;/strong&gt; — clone the &lt;a href="https://github.com/davidlastrucci/Trysil" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt;, open &lt;code&gt;Packages/&amp;lt;ver&amp;gt;/Trysil.groupproj&lt;/code&gt;, and build all&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After installation, point your project's Search Path to the compiled output directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your first entity
&lt;/h2&gt;

&lt;p&gt;An entity in Trysil is a plain Delphi class with attributes. No base class to inherit, no interface to implement.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;[TTable('Contacts')]&lt;/code&gt;&lt;/strong&gt; maps the class to the &lt;code&gt;Contacts&lt;/code&gt; table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;[TSequence('ContactsID')]&lt;/code&gt;&lt;/strong&gt; tells Trysil how the primary key is generated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;[TPrimaryKey]&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;[TColumn('...')]&lt;/code&gt;&lt;/strong&gt; mark the primary key and map each field to a column.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;[TVersionColumn]&lt;/code&gt;&lt;/strong&gt; enables optimistic locking — Trysil will check this value on every update to prevent lost writes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;[TRequired]&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;[TMaxLength]&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;[TEmail]&lt;/code&gt;&lt;/strong&gt; are validation attributes — we will cover them in depth in a later article.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;{$WARN UNKNOWN_CUSTOM_ATTRIBUTE ERROR}&lt;/code&gt;&lt;/strong&gt; turns typos in attribute names into compile-time errors. Always add this.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fields are &lt;code&gt;strict private&lt;/code&gt; with public properties. The ORM accesses them through RTTI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting to SQLite
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;TTContext&lt;/code&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  CRUD operations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Create
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var
  LContact: TTContact;
begin
  LContact := LContext.CreateEntity&amp;lt;TTContact&amp;gt;();
  LContact.Firstname := 'Ada';
  LContact.Lastname := 'Lovelace';
  LContact.Email := 'ada@example.com';
  LContext.Insert&amp;lt;TTContact&amp;gt;(LContact);
end;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always use &lt;code&gt;CreateEntity&amp;lt;T&amp;gt;&lt;/code&gt; to create new entities — it initializes internal state that the context needs to track the object.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var
  LContacts: TTList&amp;lt;TTContact&amp;gt;;
begin
  LContacts := TTList&amp;lt;TTContact&amp;gt;.Create;
  try
    // Load all contacts
    LContext.SelectAll&amp;lt;TTContact&amp;gt;(LContacts);

    for LContact in LContacts do
      WriteLn(Format('%s %s — %s', [
        LContact.Firstname,
        LContact.Lastname,
        LContact.Email]));
  finally
    LContacts.Free;
  end;
end;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To fetch a single entity by ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var
  LContact: TTContact;
begin
  LContact := LContext.Get&amp;lt;TTContact&amp;gt;(42);
  // raises an exception if not found
end;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LContact.Email := 'ada.lovelace@example.com';
LContext.Update&amp;lt;TTContact&amp;gt;(LContact);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind the scenes, Trysil generates an UPDATE statement that includes the &lt;code&gt;VersionID&lt;/code&gt; 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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Delete
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LContext.Delete&amp;lt;TTContact&amp;gt;(LContact);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The SQL table
&lt;/h2&gt;

&lt;p&gt;For completeness, here is the SQLite schema that matches our entity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Contacts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;Firstname&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;Lastname&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;Email&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;VersionID&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What is next
&lt;/h2&gt;

&lt;p&gt;In this article we defined an entity, connected to SQLite, and performed full CRUD — all without writing a single line of SQL.&lt;/p&gt;

&lt;p&gt;In the next article we will look deeper into &lt;strong&gt;entity mapping&lt;/strong&gt;: nullable fields, custom types, and how Trysil handles the relationship between your Delphi classes and your database schema.&lt;/p&gt;

&lt;p&gt;The full source code is available on &lt;a href="https://github.com/davidlastrucci/Trysil" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Trysil is an open-source project. If you find it useful, consider giving it a star on GitHub!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>delphi</category>
      <category>orm</category>
      <category>database</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Automatic audit trails and soft delete in Delphi with Trysil</title>
      <dc:creator>David Lastrucci</dc:creator>
      <pubDate>Wed, 08 Apr 2026 15:11:14 +0000</pubDate>
      <link>https://forem.com/davidlastrucci/automatic-audit-trails-and-soft-delete-in-delphi-with-trysil-4fci</link>
      <guid>https://forem.com/davidlastrucci/automatic-audit-trails-and-soft-delete-in-delphi-with-trysil-4fci</guid>
      <description>&lt;p&gt;Most ORMs handle INSERT, UPDATE, and DELETE well. But when you need to track &lt;em&gt;who&lt;/em&gt; changed a record and &lt;em&gt;when&lt;/em&gt;, or keep deleted records around instead of erasing them, you're usually on your own — writing triggers, adding boilerplate to every repository method, or bolting on an external audit library.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://github.com/davidlastrucci/Trysil" rel="noopener noreferrer"&gt;Trysil&lt;/a&gt; (an open-source ORM for Delphi), I wanted these features to be declarative: add an attribute, and the framework handles the rest. No base class to inherit, no interface to implement, no code to write beyond the attribute itself.&lt;/p&gt;

&lt;p&gt;This article shows how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Consider a typical entity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Invoices'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TSequence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'InvoicesID'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;TInvoice&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;
&lt;span class="n"&gt;strict&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TPrimaryKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'ID'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="n"&gt;FID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TTPrimaryKey&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'InvoiceNumber'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="n"&gt;FInvoiceNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;String&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Amount'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="n"&gt;FAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TVersionColumn&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'VersionID'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="n"&gt;FVersionID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TTVersion&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt;
  &lt;span class="k"&gt;property&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TTPrimaryKey&lt;/span&gt; &lt;span class="k"&gt;read&lt;/span&gt; &lt;span class="n"&gt;FID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;property&lt;/span&gt; &lt;span class="n"&gt;InvoiceNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;String&lt;/span&gt; &lt;span class="k"&gt;read&lt;/span&gt; &lt;span class="n"&gt;FInvoiceNumber&lt;/span&gt; &lt;span class="k"&gt;write&lt;/span&gt; &lt;span class="n"&gt;FInvoiceNumber&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;property&lt;/span&gt; &lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt; &lt;span class="k"&gt;read&lt;/span&gt; &lt;span class="n"&gt;FAmount&lt;/span&gt; &lt;span class="k"&gt;write&lt;/span&gt; &lt;span class="n"&gt;FAmount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you get a new requirement: &lt;em&gt;"We need to know who created each invoice and when. And when invoices are deleted, don't actually remove them — mark them as deleted so we can audit them later."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In most ORMs this means: add columns to the table, add fields to the entity, then manually set &lt;code&gt;CreatedAt := Now&lt;/code&gt; in every insert method, &lt;code&gt;UpdatedAt := Now&lt;/code&gt; in every update method, and replace every &lt;code&gt;DELETE FROM&lt;/code&gt; with an &lt;code&gt;UPDATE ... SET DeletedAt = Now&lt;/code&gt;. And don't forget to add &lt;code&gt;WHERE DeletedAt IS NULL&lt;/code&gt; to every single query.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution: six attributes
&lt;/h2&gt;

&lt;p&gt;Trysil solves this with six attributes that you place on entity fields:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attribute&lt;/th&gt;
&lt;th&gt;Populated on&lt;/th&gt;
&lt;th&gt;Field type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[TCreatedAt]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;INSERT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TTNullable&amp;lt;TDateTime&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[TCreatedBy]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;INSERT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;String&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[TUpdatedAt]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UPDATE&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TTNullable&amp;lt;TDateTime&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[TUpdatedBy]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;UPDATE&lt;/td&gt;
&lt;td&gt;&lt;code&gt;String&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[TDeletedAt]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;DELETE (soft)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TTNullable&amp;lt;TDateTime&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[TDeletedBy]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;DELETE (soft)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;String&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here's the same invoice entity with full change tracking and soft delete:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Invoices'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TSequence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'InvoicesID'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;TInvoice&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;
&lt;span class="n"&gt;strict&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TPrimaryKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'ID'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="n"&gt;FID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TTPrimaryKey&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'InvoiceNumber'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="n"&gt;FInvoiceNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;String&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Amount'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="n"&gt;FAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TCreatedAt&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'CreatedAt'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="n"&gt;FCreatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TTNullable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;TDateTime&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;

  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TCreatedBy&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'CreatedBy'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="n"&gt;FCreatedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;String&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TUpdatedAt&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'UpdatedAt'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="n"&gt;FUpdatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TTNullable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;TDateTime&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;

  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TUpdatedBy&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'UpdatedBy'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="n"&gt;FUpdatedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;String&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TDeletedAt&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'DeletedAt'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="n"&gt;FDeletedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TTNullable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;TDateTime&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;

  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TDeletedBy&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'DeletedBy'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="n"&gt;FDeletedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;String&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TVersionColumn&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'VersionID'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="n"&gt;FVersionID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TTVersion&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt;
  &lt;span class="k"&gt;property&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TTPrimaryKey&lt;/span&gt; &lt;span class="k"&gt;read&lt;/span&gt; &lt;span class="n"&gt;FID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;property&lt;/span&gt; &lt;span class="n"&gt;InvoiceNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;String&lt;/span&gt; &lt;span class="k"&gt;read&lt;/span&gt; &lt;span class="n"&gt;FInvoiceNumber&lt;/span&gt; &lt;span class="k"&gt;write&lt;/span&gt; &lt;span class="n"&gt;FInvoiceNumber&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;property&lt;/span&gt; &lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt; &lt;span class="k"&gt;read&lt;/span&gt; &lt;span class="n"&gt;FAmount&lt;/span&gt; &lt;span class="k"&gt;write&lt;/span&gt; &lt;span class="n"&gt;FAmount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;property&lt;/span&gt; &lt;span class="n"&gt;CreatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TTNullable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;TDateTime&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;read&lt;/span&gt; &lt;span class="n"&gt;FCreatedAt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;property&lt;/span&gt; &lt;span class="n"&gt;CreatedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;String&lt;/span&gt; &lt;span class="k"&gt;read&lt;/span&gt; &lt;span class="n"&gt;FCreatedBy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;property&lt;/span&gt; &lt;span class="n"&gt;UpdatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TTNullable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;TDateTime&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;read&lt;/span&gt; &lt;span class="n"&gt;FUpdatedAt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;property&lt;/span&gt; &lt;span class="n"&gt;UpdatedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;String&lt;/span&gt; &lt;span class="k"&gt;read&lt;/span&gt; &lt;span class="n"&gt;FUpdatedBy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;property&lt;/span&gt; &lt;span class="n"&gt;DeletedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TTNullable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;TDateTime&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;read&lt;/span&gt; &lt;span class="n"&gt;FDeletedAt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;property&lt;/span&gt; &lt;span class="n"&gt;DeletedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;String&lt;/span&gt; &lt;span class="k"&gt;read&lt;/span&gt; &lt;span class="n"&gt;FDeletedBy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No other code changes. The ORM does the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens at runtime
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Insert
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="n"&gt;LInvoice&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;LContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateEntity&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TInvoice&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;LInvoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvoiceNumber&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;'INV-2026-001'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;LInvoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;1500.00&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;LContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Insert&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TInvoice&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;LInvoice&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before executing the INSERT, the resolver automatically sets &lt;code&gt;CreatedAt&lt;/code&gt; to the current timestamp and &lt;code&gt;CreatedBy&lt;/code&gt; to the current user. You never touch these fields yourself.&lt;/p&gt;

&lt;p&gt;The generated SQL looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;Invoices&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InvoiceNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CreatedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CreatedBy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;VersionID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(:&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;p3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;p4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Update
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="n"&gt;LInvoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Amount&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;1750.00&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;LContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TInvoice&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;LInvoice&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resolver sets &lt;code&gt;UpdatedAt&lt;/code&gt; and &lt;code&gt;UpdatedBy&lt;/code&gt; automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;Invoices&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;InvoiceNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UpdatedAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;p3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UpdatedBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;p4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;VersionID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VersionID&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;VersionID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ver&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the optimistic locking in the WHERE clause — &lt;code&gt;[TVersionColumn]&lt;/code&gt; works alongside change tracking.&lt;/p&gt;

&lt;h3&gt;
  
  
  Delete (soft)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="n"&gt;LContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TInvoice&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;LInvoice&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the entity has a &lt;code&gt;[TDeletedAt]&lt;/code&gt; field, this does &lt;strong&gt;not&lt;/strong&gt; execute a &lt;code&gt;DELETE FROM&lt;/code&gt;. Instead it runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;Invoices&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;DeletedAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DeletedBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;p2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;VersionID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VersionID&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;VersionID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ver&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The record stays in the database. It's just marked as deleted.&lt;/p&gt;

&lt;h3&gt;
  
  
  Queries automatically exclude soft-deleted records
&lt;/h3&gt;

&lt;p&gt;Every SELECT generated by Trysil checks for the presence of &lt;code&gt;[TDeletedAt]&lt;/code&gt; on the entity. If found, it appends &lt;code&gt;DeletedAt IS NULL&lt;/code&gt; to the WHERE clause:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="n"&gt;LContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SelectAll&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TInvoice&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;LInvoices&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Generated: SELECT ... FROM Invoices WHERE DeletedAt IS NULL
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This happens transparently. You don't add any filter — the ORM knows that if you declared &lt;code&gt;[TDeletedAt]&lt;/code&gt;, you want soft-deleted records hidden by default.&lt;/p&gt;

&lt;h3&gt;
  
  
  When you need to see deleted records
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;IncludeDeleted&lt;/code&gt; on the filter builder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="n"&gt;LFilter&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;LContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateFilterBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TInvoice&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IncludeDeleted&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;LContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TInvoice&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;LInvoices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LFilter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Generated: SELECT ... FROM Invoices (no DeletedAt filter)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful for admin panels, audit views, or data recovery.&lt;/p&gt;

&lt;h2&gt;
  
  
  Providing the current user
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;*By&lt;/code&gt; attributes need to know who the current user is. Trysil doesn't impose any authentication mechanism — instead, you provide a callback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="n"&gt;LContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OnGetCurrentUser&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt;
  &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;String&lt;/span&gt;
  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;GetCurrentSessionUser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// your logic here
&lt;/span&gt;  &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This callback is invoked by the resolver right before executing INSERT, UPDATE, or soft DELETE. If not assigned, an empty string is written — so the &lt;code&gt;*By&lt;/code&gt; fields are optional. You can use only the &lt;code&gt;*At&lt;/code&gt; attributes if you just need timestamps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mix and match
&lt;/h2&gt;

&lt;p&gt;You don't have to use all six attributes. Common combinations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timestamps only&lt;/strong&gt; (no user tracking):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TCreatedAt&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'CreatedAt'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;FCreatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TTNullable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;TDateTime&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TUpdatedAt&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'UpdatedAt'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;FUpdatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TTNullable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;TDateTime&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Soft delete only&lt;/strong&gt; (no creation/update tracking):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight pascal"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TDeletedAt&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'DeletedAt'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;FDeletedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TTNullable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;TDateTime&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Full audit trail&lt;/strong&gt; — all six, as shown in the invoice example above.&lt;/p&gt;

&lt;p&gt;Each entity can have its own combination. There's no global setting to toggle.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works internally
&lt;/h2&gt;

&lt;p&gt;The implementation touches three layers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Mapping&lt;/strong&gt; — When Trysil first encounters an entity class, it scans its RTTI for the six attributes and builds a &lt;code&gt;TTChangeTrackingMap&lt;/code&gt; for each phase (created, updated, deleted). Each map holds references to the &lt;code&gt;ChangedAt&lt;/code&gt; and &lt;code&gt;ChangedBy&lt;/code&gt; column mappings. This scan happens once and is cached.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Resolver&lt;/strong&gt; — Before executing any command, the resolver calls &lt;code&gt;ApplyChangeTracking&lt;/code&gt; with the appropriate map. This sets the timestamp via &lt;code&gt;TTNullable&amp;lt;TDateTime&amp;gt;.Create(Now)&lt;/code&gt; and the user string via the &lt;code&gt;OnGetCurrentUser&lt;/code&gt; callback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. SQL generation&lt;/strong&gt; — When &lt;code&gt;[TDeletedAt]&lt;/code&gt; is present, the resolver switches from &lt;code&gt;CreateDeleteCommand&lt;/code&gt; (which generates &lt;code&gt;DELETE FROM&lt;/code&gt;) to &lt;code&gt;CreateSoftDeleteCommand&lt;/code&gt; (which generates an &lt;code&gt;UPDATE&lt;/code&gt; targeting only the deleted tracking columns and the version column). On the SELECT side, &lt;code&gt;AddSoftDeleteWhere&lt;/code&gt; injects the &lt;code&gt;DeletedAt IS NULL&lt;/code&gt; condition unless &lt;code&gt;IncludeDeleted&lt;/code&gt; is set.&lt;/p&gt;

&lt;p&gt;The soft delete command also skips relation checks (&lt;code&gt;CheckRelations&lt;/code&gt;), since the record isn't actually being removed — foreign key constraints don't apply.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database table
&lt;/h2&gt;

&lt;p&gt;The corresponding table just needs the extra columns. For example, with PostgreSQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;Invoices&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;InvoiceNumber&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;Amount&lt;/span&gt; &lt;span class="nb"&gt;DOUBLE&lt;/span&gt; &lt;span class="nb"&gt;PRECISION&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;CreatedAt&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;CreatedBy&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;UpdatedAt&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;UpdatedBy&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;DeletedAt&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;DeletedBy&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;VersionID&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No triggers, no stored procedures. The ORM handles everything at the application level.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Adding audit trails and soft delete to a Delphi application usually means writing repetitive code in every data access method. With Trysil's attribute-based approach, you declare the behavior once on the entity and the framework enforces it consistently across all operations.&lt;/p&gt;

&lt;p&gt;The feature works with all four supported databases (SQLite, PostgreSQL, SQL Server, Firebird) and composes naturally with the rest of the framework — optimistic locking, identity map, fluent query builder, JSON serialization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/davidlastrucci/Trysil" rel="noopener noreferrer"&gt;github.com/davidlastrucci/Trysil&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Documentation: &lt;a href="https://davidlastrucci.github.io/trysil" rel="noopener noreferrer"&gt;davidlastrucci.github.io/trysil&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Install: available on &lt;a href="https://getitnow.embarcadero.com/trysil-delphi-orm/" rel="noopener noreferrer"&gt;GetIt&lt;/a&gt; and via &lt;code&gt;boss install davidlastrucci/Trysil&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have questions or feedback, feel free to &lt;a href="https://github.com/davidlastrucci/Trysil/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; on GitHub.&lt;/p&gt;

</description>
      <category>delphi</category>
      <category>database</category>
      <category>orm</category>
      <category>objectpascal</category>
    </item>
    <item>
      <title>Trysil - API REST made simple</title>
      <dc:creator>David Lastrucci</dc:creator>
      <pubDate>Thu, 20 Nov 2025 21:21:00 +0000</pubDate>
      <link>https://forem.com/davidlastrucci/trysil-api-rest-made-simple-40j1</link>
      <guid>https://forem.com/davidlastrucci/trysil-api-rest-made-simple-40j1</guid>
      <description>&lt;p&gt;If you've ever built REST APIs, you know the drill: define your data model, write your database queries, map results to objects, handle serialization, write controllers... and that's before you even get to the business logic!&lt;/p&gt;

&lt;p&gt;What if I told you that with Trysil, a Delphi ORM, you can skip most of that boilerplate and go from your data model to a working API in just a few steps?&lt;/p&gt;

&lt;p&gt;Let me show you how.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Traditional Approaches
&lt;/h2&gt;

&lt;p&gt;When building REST APIs the traditional way, you often end up writing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL queries (or stored procedures)&lt;/li&gt;
&lt;li&gt;Data mapping code&lt;/li&gt;
&lt;li&gt;Validation logic&lt;/li&gt;
&lt;li&gt;CRUD operations for each entity&lt;/li&gt;
&lt;li&gt;Serialization/deserialization code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It works, but it's repetitive and time-consuming. For each new entity, you're basically copying and pasting the same patterns over and over.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Trysil
&lt;/h2&gt;

&lt;p&gt;Trysil is a Delphi ORM that helps you focus on what matters: your business logic. Once you've defined your entity model, Trysil handles the heavy lifting of persistence, queries, and data mapping.&lt;/p&gt;

&lt;p&gt;Let's see how simple it can be with a real example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Define Your Entity Model
&lt;/h2&gt;

&lt;p&gt;First, we define our TCustomer entity class. With Trysil, you simply create a plain Delphi class and decorate it with attributes to map it to your database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[TTable('Customers')]
TCustomer = class
strict private
  [TPrimaryKey]
  [TColumn('ID')]
  FID: TTPrimaryKey;

  [TRequired]
  [TMaxLength(100)]
  [TColumn('Firstame')]
  FFirstname: String;

  [TRequired]
  [TMaxLength(100)]
  [TColumn('Lastname')]
  FLastname: String;

  [TMaxLength(255)]
  [TColumn('Email')]
  FEmail: String;

  [TVersionColumn]
  [TColumn('VersionID')]
  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;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! Notice how clean and readable it is. The attributes tell Trysil everything it needs to know about the database mapping.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Create Your API Controller
&lt;/h2&gt;

&lt;p&gt;Now here's where the magic happens. To expose this entity through a REST API, we create a controller. Thanks to Trysil handling all the persistence logic, our controller can be incredibly simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[TUri('/customer')]
TCustomerController = class(TReadWriteController&amp;lt;TCustomer&amp;gt;)
end;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at that! With just a few lines of code, we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GET to retrieve customers&lt;/li&gt;
&lt;li&gt;POST to create a new customer&lt;/li&gt;
&lt;li&gt;PUT to update existing one&lt;/li&gt;
&lt;li&gt;DELETE to remove a customer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Trysil takes care of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database connections&lt;/li&gt;
&lt;li&gt;SQL generation&lt;/li&gt;
&lt;li&gt;Object-relational mapping&lt;/li&gt;
&lt;li&gt;Transactions&lt;/li&gt;
&lt;li&gt;Error handling&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;What you end up with is a clean, maintainable codebase where:&lt;/p&gt;

&lt;p&gt;Your entity model is the single source of truth&lt;br&gt;
Controllers are thin and focused on HTTP concerns&lt;br&gt;
No SQL strings scattered throughout your code&lt;br&gt;
Easy to test and extend&lt;/p&gt;

&lt;p&gt;This is the beauty of using an ORM like Trysil: you spend less time on plumbing and more time building features that matter to your users.&lt;/p&gt;

</description>
      <category>trysil</category>
      <category>delphi</category>
      <category>orm</category>
    </item>
    <item>
      <title>Trysil - Multi-tenat API REST</title>
      <dc:creator>David Lastrucci</dc:creator>
      <pubDate>Tue, 26 Nov 2024 15:54:28 +0000</pubDate>
      <link>https://forem.com/davidlastrucci/multitenat-api-rest-1lgl</link>
      <guid>https://forem.com/davidlastrucci/multitenat-api-rest-1lgl</guid>
      <description>&lt;p&gt;Run "Create new Trysil multi-tenant API REST" from the Trysil menu.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkop6th3sopk1oqgjoxty.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkop6th3sopk1oqgjoxty.png" alt="Create new Trysil multi-tenant API REST" width="288" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choose the folder, the name of the project, and whether to download the project template from HTTP (the template on the HTTP server is usually more up-to-date).&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ficq7k4p3we6irppbmdiu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ficq7k4p3we6irppbmdiu.png" alt="Project options" width="626" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter the settings for your API REST: the base uri, the port, whether the API requires authentication, and whether it should write the HTTP traffic log to the database.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyzas079m73b3552ec5zm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyzas079m73b3552ec5zm.png" alt="API REST options" width="626" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have decided to write the HTTP traffic log to a database, you will be asked for the type of database (you can choose between Firebird, Microsoft SQL Server, PostgreSQL or SQLite) and all the other parameters for the connection.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyba06ftuhlc2nxvwvqq4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyba06ftuhlc2nxvwvqq4.png" alt="Log database options" width="626" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enter the settings for the Windows service: the service name, name, and description that will appear in the list of services.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvnue93zzxh97hioiwopo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvnue93zzxh97hioiwopo.png" alt="Service options" width="626" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you need to enter the settings for the root tenant database (localhost) of your API REST.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frx0z2pfya1ybvbnocjzh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frx0z2pfya1ybvbnocjzh.png" alt="Tenant database options" width="626" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You're done, your API REST is ready.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzrnpdhynymlkyyu48b82.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzrnpdhynymlkyyu48b82.png" alt="Delphi project" width="386" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;

&lt;p&gt;Use Trysil to draw the Entity Model, create the scripts for the database, create the model and controller classes of your new API REST.&lt;/p&gt;

&lt;p&gt;To learn more about "&lt;strong&gt;Trysil - Delphi ORM&lt;/strong&gt;" visit the link to the Github project:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/davidlastrucci/trysil" rel="noopener noreferrer"&gt;https://github.com/davidlastrucci/trysil&lt;/a&gt;&lt;/p&gt;

</description>
      <category>trysil</category>
      <category>delphi</category>
      <category>orm</category>
    </item>
    <item>
      <title>Trysil - TWhereClause</title>
      <dc:creator>David Lastrucci</dc:creator>
      <pubDate>Mon, 19 Feb 2024 09:26:49 +0000</pubDate>
      <link>https://forem.com/davidlastrucci/trysil-twhereclause-5dpf</link>
      <guid>https://forem.com/davidlastrucci/trysil-twhereclause-5dpf</guid>
      <description>&lt;p&gt;&lt;strong&gt;Introduzione&lt;/strong&gt;&lt;br&gt;
Uno degli attributi disponibili in "Trysil - Delphi ORM" è [TWhereClauseAttribute].&lt;/p&gt;

&lt;p&gt;Cerco, in questo articolo, di spiegarne l'utilità.&lt;br&gt;
&lt;br&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;br&gt;
Prendiamo ad esempio la seguente tabella del database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE Users(
  ID int NOT NULL,
  Firstname nvarchar(255) NOT NULL,
  Lastname nvarchar(255) NOT NULL,
  UserType int NOT NULL,
  VersionID int NOT NULL,
  PRIMARY KEY (ID)
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stabiliamo che la colonna UserType potrà contenere i valori 0 (Insegnante) e 1 (Studente).&lt;/p&gt;

&lt;p&gt;Aggiungiamo un indice alla tabella per ottimizzare i filtri:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE INDEX Users_Index1 ON Users (UserType);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;&lt;strong&gt;TUser&lt;/strong&gt;&lt;br&gt;
Definiamo il modello astratto per l'entità TUser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ TUser }

  [TTable('Users')]
  [TSequence('UsersID')]
  TUser = class abstract
  strict private
    [TPrimaryKey]
    [TColumn('ID')]
    FID: TTPrimaryKey;

    [TColumn('Firstname')]
    FFirstname: String;

    [TColumn('Lastname')]
    FLastname: String;

    [TVersionColumn]
    [TColumn('VersionID')]
    FVersionID: TTVersion;
  strict protected
    [TColumn('UserType')]
    FUserType: Integer;
  public
    property ID: TTPrimaryKey read FID;
    property Firstname: String read FFirstname write FFirstname;
    property Lastname: String read FLastname write FLastname;
    property VersionID: TTVersion read FVersionID;
  end;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;&lt;strong&gt;TTeacher&lt;/strong&gt;&lt;br&gt;
Specializziamo l'entità TTeacher:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ TTeacher }

  [TWhereClause('UserType = 0')]
  TTeacher = class(TUser)
  public
    procedure AfterConstruction; override;
  end;

// ...

procedure TTeacher.AfterConstruction;
begin
  inherited AfterConstruction;
  FUserType := 0;
end;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;&lt;strong&gt;TStudent&lt;/strong&gt;&lt;br&gt;
Specializziamo poi l'entità TStudent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ TStudent }

  [TWhereClause('UserType = 1')]
  TStudent = class(TUser)
    procedure AfterConstruction; override;
  end;

// ...

procedure TStudent.AfterConstruction;
begin
  inherited AfterConstruction;
  FUserType := 1;
end;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;&lt;strong&gt;Trysil&lt;/strong&gt;&lt;br&gt;
Adesso siamo in grado di utilizzare TTeacher e TStudent con Trysil come se le due entità fossero persistite su due tabelle separate, in realtà entrambe andranno ad eseguire tutte le operazioni di CRUD sulla tabelle Users del database.&lt;/p&gt;

&lt;p&gt;Per saperne di più su Trysil - Delphi ORM visita il link al progetto:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/davidlastrucci/Trysil"&gt;https://github.com/davidlastrucci/Trysil&lt;/a&gt;&lt;/p&gt;

</description>
      <category>trysil</category>
      <category>delphi</category>
      <category>orm</category>
    </item>
    <item>
      <title>Trysil - Delphi ORM</title>
      <dc:creator>David Lastrucci</dc:creator>
      <pubDate>Mon, 19 Feb 2024 09:19:34 +0000</pubDate>
      <link>https://forem.com/davidlastrucci/trysil-delphi-orm-46m0</link>
      <guid>https://forem.com/davidlastrucci/trysil-delphi-orm-46m0</guid>
      <description>&lt;p&gt;Perché continuare a scrivere codice così:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query.SQL.Text :=
  'SELECT I.ID AS InvoiceID, I.Number, ' +
  'C.ID AS CustomerID, C.Name AS CustomerName, ' +
  'U.ID AS CountryID, U.Name AS CountryName ' +
  'FROM Invoices AS I ' +
  'INNER JOIN Customers AS C ON C.ID = I.CustomerID ' +
  'INNER JOIN Countries AS U ON U.ID = C.CountryID ' +
  'WHERE I.ID = :InvoiceID';
Query.ParamByName('InvoiceID').AsInteger := 1;
Query.Open;

ShowMessage(
  Format('Invoice No: %d, Customer: %s, Country: %s', [
    Query.FieldByName('Number').AsInteger,
    Query.FieldByName('CustomerName').AsString,
    Query.FieldByName('CountryName').AsString])); 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;br&gt;Quando c'è la possibilità di scriverlo così?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LInvoice := FContext.Get&amp;lt;TInvoice&amp;gt;(1);
ShowMessage(
  Format('Invoice No: %d, Customer: %s, Country: %s', [
    LInvoice.Number,
    LInvoice.Customer.Name,
    LInvoice.Customer.Country.Name]));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Per maggiori informazioni visitail link al progetto Trysil:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/davidlastrucci/Trysil"&gt;https://github.com/davidlastrucci/Trysil&lt;/a&gt;&lt;/p&gt;

</description>
      <category>trysil</category>
      <category>delphi</category>
      <category>orm</category>
    </item>
  </channel>
</rss>
