TMS Aurelius is an ORM framework for Delphi developers. The framework is written in Delphi, and it is a proprietary product developed by TMS Software. Aurelius has been available in the market for a number of years, and it enjoys a good customer base. The framework plays its part in a wide range of applications with different scope and different requirements. Aurelius is not limited to Windows platforms, and it can work efficiently on macOS, iOS, Android, and Linux, and it supports all Delphi versions from XE2 to the latest.
In this chapter, we start clean and install Aurelius from scratch. Then, the chapter explores some fundamental concepts (database connectivity, SQL dialects, etc.) that allow us to use Aurelius, and we look at our first code snippets that put Aurelius to work for us.
Installation
Aurelius comes in two versions: a trial version and a licensed one. The difference is that with the trial version you only get the DCU files; it is intended for noncommercial use and it expires at some point. There is a free version as well which does not expire and you can use it for commercial applications, but there are some limitations. For more information about the free version, contact TMS Software directly. The next steps guide you how to install the trial and the licensed versions.
Trial Version
- 1.
Go to the product’s web page on TMS Software site at this address https://tmssoftware.com/site/aurelius.asp or visit https://tmssoftware.com and select “Business Tools” from the tiles with their products. This will load a page with several interesting pieces of information. At the bottom of the page, you can find the link to access the Aurelius web page.
- 2.In Aurelius page, scroll at the bottom of the page. You will be able to see a grayed area with download links for different Delphi versions (Figure 2-1). Select the link for the Delphi version you use and download the file in your computer.
Note In the same page you can also find the technical manual, as shown in Figure 2-1. This is also copied by the installer, but if you wish to have the manual without installing the package, download it from the link.
- 3.
The downloaded file is a compressed zip file that contains all the products in the TMS Business Tools package. Extract the files and execute the installer.
- 4.
Let the installer complete the installation of all packages.
- 5.
If you haven’t changed the default location, Aurelius is now installed in Documents mssoftwareusinessrioAurelius.
Note: The businessrio part in the preceding path indicates the Delphi version you install the package for. In my case, I installed Aurelius for Delphi 10.3 Rio.
- 6.Now, you can launch Delphi and check that the package has been installed correctly. In the splash screen, you should be able to see the BIZ logo and a reference to the package similar to Figure 2-2.
- 7.Additionally, if you look at the Options ➤ Library paths for different platforms, you should be able to see the paths to the compiled packages (Figure 2-3).
- 8.
Now, you are ready to use Aurelius in your Delphi programs.
Note
The trial version of the package installs compiled .dcu units for all the available platforms and for the Release configuration. Source files for several database drivers have also been installed. Additionally, you can find Demos and Documentation in their respective folders.
Licensed Version
If you are a registered user with TMS Software, you can log in to your account in their web site. In your account, you are able to download all the products you have purchased license for. Note that licensed installers build packages for all the installed Delphi version in your system, eliminating the need to run separate installation for each Delphi IDE.
Prerequisite Packages
TMS Aurelius requires the TMS Business Core Library (BCL) to be installed in the system. Download the compressed setup file from your account, unzip it, and install the package.
Installation
- 1.
Make sure TMS Business Core Library (BCL) is installed (see previous section).
- 2.Download the compressed setup file for Aurelius from your account, unpack it, and run the installer (Figure 2-4).
- 3.The next page in the installer invites you to accept the license, and then you have to enter your registration email and code (supplied by TMS). This step requires your system to be able to access the Internet as the installer validates your license online.
- 4.
Verify the settings that are presented in the following pages of the installer and allow it to copy all the necessary files to your system.
- 5.
When all files are copied, the installer will launch the Package Rebuild Tool to build the packages for all the installed Delphi versions and all available releases. For more details, see the next section.
- 6.
When the building of the package finishes, Aurelius is installed and it is ready to use. You should be able to see the TMS Business product line logo in the Delphi splash screen as in Figure 2-2 and also inspect the relevant library paths.
Package Rebuild Tool
The Package Rebuild Tool is a stand-alone application that is installed with TMS Aurelius. You can find it in the Start Menu group and launch it independently. The installer, as mentioned earlier, uses this tool to generate the binaries of the Aurelius packages, but you can use it every time you want to recompile the packages for any reasons. Corrupted .dcu files or updates to the IDE may lead to unusable binary packages. In such cases and in the cases where you modify the source code directly or receive patches sent by TMS Software, you can manually rebuild the packages.
At the bottom half of the window, the available platforms are shown, and you can select which ones you would like to install or rebuild the packages for. This part of the user interface can be confusing as the design indicates that the platform selection is independent to the Delphi version. For example, if I want to build the packages for OSX32 for Delphi 10.2 but not for Delphi 10.3, I need to rebuild the packages twice; one for 10.2 with OSX32 selected and one for 10.3 with OSX32 deselected.
Note
Figure 2-6 demonstrates a very common situation with the available platform. Android and Linux are not enabled because the tool identifies these platforms either as wrongly configured or not available. In my system, Linux is not available as built platform and Android is not properly configured. If you have the same situation, check the SDK Manager in Delphi, correct any errors that appear, and rerun the Package Rebuild Tool.
TMS Subscription Manager
TMS Software offers a simpler and more automated way to manage any packages you have purchased. As a registered user, you have access to a tool called TMS Subscription Manager. This tool allows you to keep track of the installed packages and see new versions, and it facilitates the installation (and uninstallation) of the packages.
Database Connectivity
- A component that actually allows Aurelius to access the database. There are two ways for this to be defined:
Using an adapter (Adapter Mode): The adapter works as a proxy between Aurelius and a database, and it requires components by the providers of the adapter to be installed and configured properly. This option provides high flexibility and great degree of customization of the connection. FireDAC component belongs in this group.
Using a native driver (Driver Mode): This mode provides a convenient way to use a database in Aurelius without the need to install or employ any third-party software as in the adapter mode. Although the flexibility in customization is not as high as in adapter mode, this mode is a very convenient and quick way to set up the database connectivity of the framework. In the version of Aurelius at the time of writing, this mode allows native connectivity to SQLite databases and databases hosted by Microsoft SQL Server.
An SQL Dialect: When Aurelius gets the connection, it needs to know which variation of the SQL language should be used to communicate with the database. This information is passed by supplying an SQL dialect during the database connectivity setup stage. The ability to define the dialect is exposed to the developers only in the case of the adapter mode. In the native driver mode, Aurelius can work out the required dialect by looking at the selected native driver.
Databases and Connection Adapter Compatibility (Homologation) in TMS Aurelius
Adapter Database | Native | Absolute | AnyDAC | dbExpres | dbGo | DOA | ElevateDB | FireDAC | FIBPlus | IBO | IBX | NativeDB | NexusDB | SQLDirect | UniDAC | UIB | ZeosDB |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
AbsoluteDB | • | ||||||||||||||||
IBM DB2 | • | • | • | • | • | ||||||||||||
ElevateDB | • | ||||||||||||||||
FireBird (including FireBird 3) | • | • | • | • | • | • | • | • | |||||||||
Interbase | • | • | • | • | • | • | • | • | |||||||||
MS SQL Server | • | • | • | • | • | • | • | • | |||||||||
MySQL | • | • | • | • | • | • | |||||||||||
NexusDB | • | • | |||||||||||||||
Oracle | • | • | • | • | • | • | • | ||||||||||
PostgreSQL | • | • | • | ||||||||||||||
SQLAnywhere | • | • | |||||||||||||||
SQLite | • | • | • | • |
IDBConnection
In terms of coding, a connection to a database in Aurelius is represented by the IDBConnection interface . This interface is perhaps one of the most fundamental data structures in Aurelius as it makes the database operations happen. The adapters or the drivers discussed in the previous section are used to create a valid IDBConnection interface. In turn, this means that an IDBConnection is tied to an adapter or a driver and not to a database engine (although when you use an adapter that can access only one database, then the IDBConnection is ultimately tied to one database as well), and this is
- 1.
Using pure code
- 2.
Using the TAureliusConnection component
- 3.
Using the TMS Aurelius DBConnection wizard in RAD IDE
Using Code
To demonstrate how we can get an IDBConnection programmatically, we’ll create a connection to an SQLite database. Table 2-1 indicates that this can be achieved in Aurelius by either using a database adapter or natively. For this example, we are going to create an in-memory SQLite database.
Using an Adapter
- 1.
Create a new project in Delphi. It can be a VCL, FMX, or console application, but it is much easier if you choose a VCL or FMX project.
- 2.
Drop a FireDAC connection (TFDConnection) in the form (FDConnection1).
- 3.Select the connection component, right-click, and launch the connection editor (Figure 2-9). Select SQLite in Driver ID field and enter “:memory:” in the Database field as in the figure and choose OK.
- 4.
Click the OnCreate event of the form and write the following code:
After the call to the TFireDacConnectionAdapter constructor , the fConnection property holds a reference to the database and can be used to perform operations.
Using Native Drivers
Note
The native component is very useful as it requires the minimal possible code to generate a database connection and does not require any third-party components. However, it does not provide a way to access encrypted SQLite databases. If you want to achieve this, you need to use a FireDAC component.
Using the TAureliusConnection Component
- 1.
Create a new project in Delphi.
- 2.
Drop a FireDAC connection (TFDConnection) in the form (FDConnection1) as before.
- 3.Drop a TAureliusConnection component in the form (AureliusConnection1). You should be able to see two components as in Figure 2-10.
- 4.Select the Aurelius connection component, right-click, and select “Connection Settings…”. In the editor make the necessary options to connect to the FireDAC component (Figure 2-11) and select OK. You can also test the connection using the button at the bottom of the form.
- 5.Retrieve the IDBConnection using the following code:procedure TForm1.FormCreate(Sender: TObject);beginfConnection:=AureliusConnection1.CreateConnection;end;
Using the TMS Aurelius DBConnection Wizard
- 1.
Go to File ➤ New ➤ Other ➤ TMS Business in Delphi and select the TMS Aurelius DBConnection wizard as in Figure 2-12.
- 2.
In the next step (Figure 2-13), choose whether you want to connect to the database using an adapter or a native driver. For this example, I opted for the native driver to generate an SQLite database.
- 3.
The wizard creates a unit (ConnectionModule.pas) with a data module and a TAureliusConnection attached to it. The name of the module is after the connection. In my case, it is called TSQLiteSQLiteConnection and the associated variable is SQLiteSQLiteConnection. This naming convention may appear confusing to the Delphi developer as the name implies that the underlying object is a connection when it is, actually, a data module. This may not be a big problem because the only real need for this module is to generate an IDBConnection instance once. In a typical application, this will most likely be done at the beginning of the application, and the interface will be injected as needed.
- 4.
Once the data module is created and the Aurelius connection has been configured, the connection interface can be retrieved as in the following code. Note that the wizard assigns a variable to the data module, but it is not instantiated. This needs to be done manually.
ORM Paradigm in Aurelius
In the previous chapter, the ORM building elements were discussed (entity, properties, associations, criteria, projections, container). In order for Aurelius to implement these elements, the framework defines a number of attributes that can be used.
The attributes determine how the virtual object database in Aurelius is defined and managed and how the operations at the database level are performed. They also work as indicators in the code. If you browse through the code, you can understand the ORM structure by observing the attributes. To demonstrate the use of attributes in Aurelius, we will continue with the blog example from the first chapter.
Note
If you are not familiar with attributes in Delphi or you would like a refresher, visit the official Delphi documentation (Embarcadero, n.d.). For a more detailed treatment of attributes, you may find useful to check Nick Hodges’ book (Hodges, 2014).
Entity
Aurelius knows that it has to include in the virtual object database any class with the [Entity] attribute. However, at the initial stages of your code and if you follow the model-first or code-first workflow, you may declare classes that are not used anywhere in the code yet. This results in the classes being removed during the compilation and linking phase in Delphi.
As a result, those classes will not appear as tables in the underlying databases as Aurelius will not know that those classes exist. Initially, this may not pose a real concern because the next time you use the class, the linker will include it to the executable and Aurelius will update the database schema accordingly. Problems may arise when associations exist between classes that represent association endpoints where one endpoint is used in the application and another one is not yet. This will make the final executable to link only the classes from the endpoints that are used, leaving the associations at Aurelius level in nonfunctional state.
Properties
- 1.
Identifier
- 2.
Column
- 3.
Column (NULL)
- 4.
Blob
Identifier
Each entity in Aurelius should be, uniquely, identified in the framework as a record is, uniquely, identified in a table by a primary key. This is done by declaring a property of the class to use the [Id] attribute. The most common approach here is to allow the database to manage the generation of the identifier value as in the case of a primary key. This is not restrictive as you can take control of the values of the identifier and manage it in your code. Typical values include sequential numbers, but for our TPost I will use a GUID as identifier as this approach is gradually becoming more common among database engineers.
The code defines a new property ID of TGUID type and the corresponding private variable (fID). The [Id] attribute is used to declare to Aurelius that fID is the variable to be used as the identifier of the class and that Aurelius will need to generate GUID identifiers. There are more identifier engines available, and they are discussed in the manual.
If you need to declare composite identifiers in your database, just add as many [Id] properties as required. In that case, Aurelius will completely manage the generation of the identifier so the engine in the attribute should be TIdGenerator.None. However, broadly speaking, the official manual suggests that composite identifiers should be avoided.
Column
Note that the DateTime field has been declared as NOT NULL (required) in the database. If you want to declare the length of a field or its precision for numeric fields (columns), the [Column] attribute receives parameters that can accommodate these requirements. The manual provides more details on this.
Column (NULL)
In databases, columns can be empty (NULL), a concept that is different from empty fields in classes. For example, in TPost, the Content field is a string. An empty string field in the class yields length of 0; however, a null column in the database table does not as it does not have any content. This situation is managed by Aurelius with the introduction of the generic record Nullable<T> and it is used as in the following code. Note that I have declared the length of the column to 65535 which is the maximum value for VarChar in SQLite, but this seems a poor choice for full-scale production database.
Blob
The [Column] attribute provides a way to affect the behavior of TBlob. In the example code, the FImage field will not be loaded immediately when a Post instance (record) is retrieved in the code (lazy loading); instead the content will become available the very first moment the Image property is accessed. This is common behavior with blobs. We will also see this behavior again when we deal with associations.
Associations
As discussed in the previous chapter, associations are formed between endpoints (properties in entities). Aurelius implements two types of associations: one-to-many and many-to-one. If you need to implement one-to-one associations, you need to handle this by code.
Many-to-One Association
It is imperative to use both these attributes together; otherwise, you will get an error. As you can see in the code, both attributes provide a way to tailor the behavior of the foreign key relationship by supplying additional parameters. The manual includes very detailed information for both attributes.
One-to-Many Association
This tells Aurelius that the corresponding field in TPost is FUser. In other words, when Aurelius loads FPosts, it populates the list with those TPost objects that have their fUser attribute the same as the identifier FID of the current instance of TUser.
In the case where a class does not have a defined association, the same result can be achieved by supplying the [ForeignJoinColumn] attribute immediately after [ManyValuedAssociation]. More information and examples appear in the manual, but it seems that this attribute exists to cover some corner-case situations.
One-to-One Association
Lazy Loading
All the previous examples are designed in such a way that when a Post is retrieved by Aurelius, the User property will get the object with the user at the same time. Similarly, when a User is loaded, the Posts property (TList) will be created and loaded with content. This is the default (eager) loading.
Although in our examples the footprint of such approach is subtle, in full-scale applications with complex databases, it will load unnecessary content stretching the available resources. The behavior of the properties can be altered to allow loading of valid content only when they are accessed (lazy loading).
Lazy loading requires a virtual proxy, a mechanism that shows the same properties as the underlying entity but controls when the loading of the actual content is done. Aurelius provides a generic record Proxy<T> for lazy loading.
Let’s make the User property in TPost a lazy loading property. We need to use the Proxy<TUser> record . Now, we are not able to access the TUser directly, but we need to use Proxy’s properties (Value). As a last step, we need to inform the [Association] property that the relationship behaves in a lazy manner. The following code shows the changes in bold:
Next, we would like to change the loading behavior of Posts in TUser entity. This time, the property is of TList type. Proxy<T> record can be used in this case as well with a small caveat; in addition to the TPost objects, we need to manually instantiate and destroy the corresponding TList; otherwise an access violation will be raised the moment we try to retrieve the content of Posts. Aurelius provides some methods for this purpose as shown in the following code:
Object Manager (Container)
Aurelius implements the concept of the entity container by providing an Object Manager (TObjectManager). The object manager comes with all the required functionality to manipulate the underlying database. It operates on entities (objects) loaded from the database and allows the developer to save, update, and load them and perform queries in order to retrieve results. When the state of an entity changes, the object manager makes sure that the database is updated accordingly. Because the object manager holds a state of entities in memory, it is capable of managing the lifetime of the entity objects. Entities are instantiated and destroyed by the object manager automatically.
The object manager is designed to provide a lightweight buffer between developer and the database in use. It is meant to be a short-lived object. Very often, this point confuses newcomers to Aurelius. You instantiate an object manager every time you want to do a specific database-related operation like finding records or retrieving records under specific criteria. Once this operation is completed, the object manager should be destroyed. This means that keeping an object manager alive for the lifetime of an application is not a good use of the manager architecture and capabilities.
Typically, object manager objects are treated in try-except or try-finally blocks because the manager hits the database, and this may result to errors due to corrupted files, unavailable database servers, or other unpredictable factors.
This code snippet demonstrates the typical use of object managers. As you can see, we have to free the object explicitly. It is obvious that the object manager could be implemented as an interface to save the manual lifetime management, but instead it is provided as a typical class. This can be inconvenient as it generates lots of boilerplate code. In the following chapter, when we start writing code for our example application, we will see how we can do this automatically.
Criteria
The object manager provides a way to execute queries in order to retrieve results from the database. The queries are constructed by putting criteria together that represent the query you want to execute.
The simplest way to use the criteria is to take advantage of the Find<T> method in object managers. Find uses generics to determine the entity the query will return. For example, if you want to retrieve the list of posts, you can use the following code:
A call to Find<TPost>.List always creates a new TObjectList even if the query itself returns nothing. You can be confident that you always have a valid instance of postsList after such call. Consequently, you need to free the instance manually; otherwise it will lead to memory leaks.
Another point to note is the type of list the object manager returns. In the preceding example, I used a TObjectList, but you can equally use a TList. There is no real difference.
As you can observe in the code, Aurelius allows the use of relational operators as part of the criteria statements. It supports a range of operations including string manipulation and the ability to directly use SQL statements. The official technical manual provides more details on this topic.
What we did is to create an alias to the property of the entity class that represents the association. We assigned the letter u to the property User, but this is an arbitrary choice; I can use any string for alias including the string user. The important point here is that we need to create an alias to get access to the properties of the associated entity. From this point onward, we can create any criteria we need and refer back to the properties of the association by using the u. connotation. For completion, creating aliases is not the only way to access associations; SubCriteria can also be used but I find aliases much more efficient purely because it generates clear statements in the code, and I can follow the logic of the criteria very easily. But if you prefer SubCriteria, there is no real reason not to use it.
Note
Whenever you want to access an association in an entity object, you should always create an alias or subcriteria. Otherwise, Aurelius will throw an error as it will try to locate a field by that name. In the following code, Aurelius will attempt to locate the User.Name field, which will lead to an error:
...postsList:=objManager.Find<TPost> .Add(Linq['DateTime'] = Now) .Add(Linq['User.Name'] = 'John') .List;...
Projections
Projections are also provided by the object manager as they are basically extensions to the criteria. Generally speaking, we refer to projections when we want to retrieve specific values either directly from a list of entities or as a result of calculations and grouping. For all other cases, we refer to criteria.
We use Select to create a projection. Select can manage a list of projections via TProjections.ProjectionList, and there are a number of functions that can be used with it. Please refer to the official manual for the most up-to-date list of the functions.
Because projections are designed to retrieve values, it follows that they do not return lists of entities but rather lists of values. In Aurelius, each projection result is held in a TCriteriaResult object.
I personally prefer to create lists even when I have only one projection because it gives me a standardized way of building projections and also allows me to add another condition to the list very easily. In terms of accessing the projection results, you need to iterate through the list of TCriteriaResult in order to retrieve the desired value. Additionally, we need to manually free the instance of the result list.
In the case of UniqueValue, the call can return nil if there is no relevant content. In this case, we need to do a separate check using Assigned.
There are two steps here. First, I add a projection to retrieve the month of the posts using the Month projection method. Then, I group the results based on the month component of the DateTime field of TPost.
Unfortunately, this approach does not work and for good reason. The value Month is known to Aurelius only after the list of projections has been executed. Therefore, Aurelius cannot resolve it as part of the projection itself.
Automapping
The attributes to map a class to an entity provide great degree of flexibility to the developer when it comes to defining how Aurelius should treat the underlying tables, columns, and associations in the database. One drawback to this is that it may turn to a time-consuming process especially when the workflow of work puts the database first. For such cases, but, also, to provide simplicity of use, Aurelius introduces the [Automapping] attribute . Decorating a class with the [Entity] and [Automapping] attributes removes the need to visit every property and association in the class and provide explicit instructions about how Aurelius should treat each field and property. The automapping procedure follows a number of rules and conventions that can be found in the official documentation.
There is a small catch with automapping. When Aurelius identifies fields to map, it checks the name and if it starts with capital F (a commonly followed convention), it extracts the letter and names the table column after the remainder string. For example, if we have a field named FSurname, Aurelius will map it to the Surname column in the database table. In all other cases, Aurelius will add the F_ prefix. Thus, a field named fSurname will become F_fSurname.
If you want to achieve the opposite result (exclude properties and fields from automapping), you can decorate them with the [Transient] attribute.
Summary
This chapter presents TMS Aurelius. Step by step, we start by looking at the different installation options and then move to explore how ORM features as provided by the framework. This introduction sets the scene to show how Aurelius can be used in a real-life application. The next chapter presents the example application that we are going to use to put Aurelius at work.
References
Embarcadero, n.d. Attributes (RTTI). [Online] Available at: http://docwiki.embarcadero.com/RADStudio/Rio/en/Attributes_ (RTTI) [Accessed 04 03 2019].
Hodges, N., 2014. Coding in Delphi. s.l.:Nepeta Enterprises.