TMS Data Modeler and Filter Attributes
Introduction
Starting with v.5.0, TMS Aurelius offers a filtering mechanism based on attributes. This means that instead of altering the criteria before they are passed to the object manager, we can now tag the database entities with attributes and instruct the object manager to update the filter parameters.
TMS Data Modeler (DM) is a tool to help developers design a database and, in the context of this post, it automates and fine-tunes the mapping of Delphi classes to the underlying database entities. Unfortunately, TMS have not updated DM to include the filter attributes, which means the user may need to do a lot of manual editing of the final Delphi code.
In this post, we look at how we can write a script in DM to add filter attributes to the final mapped Delphi classes.
Filter Attributes
Implementing filter attributes is a three step process:
- We define the filter using the
FilterDef
attribute - We define any filter parameters using the
FilterDefParam
attribute - We decorate the entity classes with the
Filter
attribute to indicate which filter applies to what entity
Then, we instruct the object manager to use the filter every time there is a need using EnableFilter
.
⚠️ The current implementation of the filter mechanism does not work with foreign keys but only with independent properties (table columns). This is obviously a limitation and, hopefully, TMS will improve it in future releases.
Customers and Orders
Let’s consider the case of a customer (TCustomer
) who orders products or services (TOrders
). Each order can, naturally, contain multiple items (TOrderItems
). The database tables, in their simplest form, can look like Fig. 1.
Figure 1: Customers, Orders, Order Items
What we want to achieve is to add the necessary filter attributes to the Delphi classes in the final unit with the entities. The filters should, obviously, filter the orders by customer using CustomerID
field in TOrders
and filter the individual items in an order using OrderID
field in TOrderItems
. As noted earlier, filter can work only if we drop the foreign keys.
More specifically, we need to:
- Define a filter for the customers (
customer
) that bindsCustomerID
to a value (customer-id
) passed to the object manager - Decorate all the classes that have the
CustomerID
property with thecustomer
filter - Define a filter for the order(
order
) that bindsOrderID
to a value (order-id
) passed to the object manager - Decorate all the classes that have the
OrderID
property with theorder
filter
Scripts
Launch TMS Data Modeler and create the database tables. Then, select the Tools
menu in the ribbon and hit the TMS Aurelius button. This is the wizard to generate the final Delphi unit with the mapped classes. Select the Script
tab. This is where we are going to add our code to generate the attributes. The scripting mechanism is implemented by attaching events to the generation process of the final Delphi unit. The unit can be previewed before saved in the Preview
tab.
Click on the Declare Events
button. From the list of the generated events, we are interested in two of them: OnUnitGenerated
and OnClassGenerated
. The first one occurs after the code in the unit has been generated and the second one fires after each class (entity) is generated. We are going to use the first event to define the filters and the second one to decide which filter should be attached to what class.
Add the following code to the OnUnitGenerated
event:
procedure OnUnitGenerated(Args: TUnitGeneratedArgs); begin Args.CodeUnit.FindType('TCustomer').AddAttribute('FilterDef(''customer'', ''{CustomerID} = :customer-id'')'); Args.CodeUnit.FindType('TCustomer').AddAttribute('FilterDefParam(''customer'', ''customer-id'', TypeInfo(integer))'); Args.CodeUnit.FindType('TCustomer').AddAttribute('FilterDef(''order'', ''{OrderID} = :order-id'')'); Args.CodeUnit.FindType('TCustomer').AddAttribute('FilterDefParam(''order'', ''order-id'', TypeInfo(integer))'); end;
We need to define the two filters by adding the FilterDef
and FilterDefParam
attributes to the very first class in the unit. I haven’t found a way to iterate through the available classes in a unit. So, I hardcode the name of the first class (TCustomer
) and use the FindType
and AddAttributes
methods to generate the attribute.
💡If you are interested in investigating the available classes, properties and methods in the scripting language you can click on the Debug
button and select Library
from the View
menu in the new window.
In the OnClassGenerated
event, we iterate through the members of the class and attach the right filter according to the name of the member.
procedure OnClassGenerated(Args: TClassGeneratedArgs); var num: integer; item: TCodeTypeMember; begin for num:=0 to Args.CodeType.Members.Count - 1 do begin item:=Args.CodeType.Members[num]; if Copy(item.Name, 0, Length('CustomerID')) = 'CustomerID' then Args.CodeType.AddAttribute('Filter(''customer'')'); if Copy(item.Name, 0, Length('OrderID')) = 'OrderID' then Args.CodeType.AddAttribute('Filter(''order'')'); end; end;
Source Code
The DM model file with the script can be found in this repository.