© John Kouraklis 2019
John KouraklisIntroducing Delphi ORMhttps://doi.org/10.1007/978-1-4842-5013-6_8

8. TMS Data Modeler

John Kouraklis1 
(1)
London, UK
 

All the code we have developed up to this point centers on the code-first approach. This means that our database design is based on the needs we identify when we get our hands in the application design. As developers, we first consider the business side of an application and then we resolve to object-oriented patterns in order to build a software solution.

As discussed in Chapter 1, code-first approach is not the only available path developers can take. They can equally start from modeling the back end of the software solution they are designing (model-first) or, in the very real and common cases where software needs to be developed on existing databases, they look at the database first and construct the application to fit in the database (database-first pattern).

The Application

Aurelius, as we have seen, makes code-first design a breeze. For the other approaches, we turn our focus to TMS Data Modeler. Data Modeler is a proprietary application developed and maintained by TMS Software and comes as a separate product. It provides a flexible and easy way to manage both model-first and database-first designs. In the next sections, we explore the two approaches using the CallCentre project. There is a wealth of features in the application, and you can find more details in the technical manual that accompanies the product.

For our examples, there are three points to look at:
  • Diagram (Model) Editor: This is accessible from the left-side panel and provides a graphical representation of the entities and the tables in a database (Figure 8-1). Data Modeler organizes graphs of entities into diagrams.

  • Importing of existing databases: In the cases where an existing database is being inherited to the project, you can import the database schema and allow Data Modeler generate the tables and the entities automatically.

  • Export to Aurelius entities: This is perhaps the most valuable feature of Data Modeler. Once the entities have been defined, Data Modeler can generate a Delphi unit with the declarations of the classes you need for Aurelius.

../images/481234_1_En_8_Chapter/481234_1_En_8_Fig1_HTML.jpg
Figure 8-1

The Data Modeler application (diagrams)

Note

The examples in this chapter require the Data Modeler application which is a separate product sold by TMS Software. Please check the product page in the company’s web site.

Model-First

In this approach, developers work at a conceptual level. They are not concerned with the database specificities and focus on implementing entity designs to support the business value and challenges of the applications they are developing. Let’s move on and create a model for our CallCentre project . You can find the full Data Modeler project in the files that come with this book (CallCentre.dgp).
  1. 1.

    Create a new project in Data Modeler by selecting the FileNewNew Project menu.

     
  2. 2.
    In the next dialog box, select SQLite as the database of our preference (Figure 8-2). This is the database engine we use in our examples, but in case you want to change to something else Data Modeler allows you to migrate a project from one database to another.
    ../images/481234_1_En_8_Chapter/481234_1_En_8_Fig2_HTML.jpg
    Figure 8-2

    Target database dialog when a new project is generated

     
  3. 3.
    Select Diagrams from the left-sidebar and you will see a diagram already created (Main Diagram). Select the Design tab in the Ribbon and click Table item (Figure 8-3). After you select the button in the Ribbon, you need to click the white space in the Main Diagram tab to see the details of the table.
    ../images/481234_1_En_8_Chapter/481234_1_En_8_Fig3_HTML.jpg
    Figure 8-3

    Inserting a new table in the project

     
  4. 4.
    Add the details of the Department entity as seen in Figure 8-4. Two fields worth mentioning in this form are the Logic Type and the Physical Type fields. You can only change the logic type of a field in an entity. This is where you declare what kind of data you are expecting the field to hold. Then, Data Modeler works out the actual field type in the database. It is able to do this because when we created the project we declared SQLite as the underlying database engine.
    ../images/481234_1_En_8_Chapter/481234_1_En_8_Fig4_HTML.jpg
    Figure 8-4

    The details of Department entity

     
  5. 5.
    Create the entities for Agent and Call as in Figures 8-5 and 8-6.
    ../images/481234_1_En_8_Chapter/481234_1_En_8_Fig5_HTML.jpg
    Figure 8-5

    The details of Agent entity

    ../images/481234_1_En_8_Chapter/481234_1_En_8_Fig6_HTML.jpg
    Figure 8-6

    The details of Call entity

     
  6. 6.
    At this stage, we have the entities in our model. The next step is to create the associations between them. In Call entity, there is the DepartmentID field that represents the foreign key to the ID field in Department entity. Go back to the Main Diagram and select the Non-ID Relationship from the Ribbon (Design tab) as in Figure 8-7. Non-ID relationship in Aurelius is an association between two entities where the foreign key is not part of the primary key; for example, if we had a compound primary key in Call comprising the department (e.g., 1-Air Condition), then we would have to link Department and Call via a normal relationship and not a non-ID relationship. The latter type is the most commonly used.
    ../images/481234_1_En_8_Chapter/481234_1_En_8_Fig7_HTML.jpg
    Figure 8-7

    Inserting a non-ID relationship

     
Using the mouse, drag a line from Department to Call. This will open the Relationship Editor (Figure 8-8). Click the Child Table ID field and you will be able to select a different value. This is not very obvious in the user interface; you may need to double-click the ID field. Select DepartmentID as the foreign key in Call. In the same form, you can adjust how you want the relationship to behave in the case of a deletion or update of a Department. Then click OK.
../images/481234_1_En_8_Chapter/481234_1_En_8_Fig8_HTML.jpg
Figure 8-8

The Relationship Editor

  1. 7.

    In a similar way, add a relationship between Agent and Call.

     

At this stage, you should have all the entities defined and the relationship declared. The main diagram should look like the one in Figure 8-1.

Database-First

The other approach to work with databases and ORM frameworks is to start from a database. This means that you or someone else has already created the tables, the field, and all the associations the database (and the applications that use it) needs. It is very handy to be able to inspect the database and, somehow, regenerate the model of the database and, consequently, the entities in our ORM. We can achieve this in Data Modeler via the reverse engineering feature.

For completeness, it should be noted that TAureliusConnection allows the generation of entity classes by scanning the attached database directly from the IDE. This is a shortcut to the database-first approach. Although this functionality is very handy, it does not allow any level of customization. On the other hand, Data Modeler offers a wealth of options.

In order to demonstrate how it works, we are going to use a database from the previous chapters. You can generate the database running one of the projects we developed in Chapter 7.
  1. 1.

    Create a new project in Data Modeler by selecting the FileNewImport from Database menu.

     
  2. 2.
    In the wizard, create a new SQLite connection (Figure 8-9) and then locate the database file (Figure 8-10).
    ../images/481234_1_En_8_Chapter/481234_1_En_8_Fig9_HTML.jpg
    Figure 8-9

    The reverse engineering wizard (new connection)

    ../images/481234_1_En_8_Chapter/481234_1_En_8_Fig10_HTML.jpg
    Figure 8-10

    The reverse engineering wizard (database file)

     
  3. 3.

    Select Import and complete the wizard. Now, you have the model based on the database file and you are able to do any amendments you wish.

     

Export to Aurelius

In the previous sections, we created a model from scratch without getting into any details from any database, and we imported the scheme from a database and generated the corresponding model. The next step is to link back to the ORM framework.

Data Modeler provides the ability to export the model to Aurelius. It is one of the most valuable features in my view as it removes the need to write code manually. You can create one or more units with entities, fields, properties, and associations very easily which increases productivity a lot. You may wish to do some adjustments, but it can save you a lot of time and effort, especially if you are dealing with massive databases.

Entities

It is very easy to create a unit with all the entities in your database. Here we are going to create the basic entities we used in the CallCentre project.
  1. 1.
    Go to Tools tab in the Ribbon and select TMS Aurelius (Figure 8-11).
    ../images/481234_1_En_8_Chapter/481234_1_En_8_Fig11_HTML.jpg
    Figure 8-11

    The TMS Aurelius export option in Data Modeler

     
  2. 2.
    The following form is revealed (Figure 8-12).
    ../images/481234_1_En_8_Chapter/481234_1_En_8_Fig12_HTML.jpg
    Figure 8-12

    TMS Aurelius Export form (General Settings)

     
  3. 3.
    Click the Mappings tab and keep the Agent, Call, and Department entities checked. This tells the wizard to generate Aurelius code for these entities only (Figure 8-13).
    ../images/481234_1_En_8_Chapter/481234_1_En_8_Fig13_HTML.jpg
    Figure 8-13

    TMS Aurelius Export form (Mappings)

     
The options in the form are self-explanatory. You have the ability to affect the generated code by modifying the name of the properties or the data type itself and preview the associations by switching to the Associations tab.
  1. 4.
    Switch to the Many-Valued Associations tab (Figure 8-14). If you have the Agent entity selected, you will see that the wizard can generate a list which links back to the associated endpoint (Call). This is something we added manually when we were exploring the many-valued associations, and the lists are not compulsory for the ORM framework to work. Nevertheless, it is very helpful as it allows the drilling down of data.
    ../images/481234_1_En_8_Chapter/481234_1_En_8_Fig14_HTML.jpg
    Figure 8-14

    TMS Aurelius Export form (Preview)

     
  2. 5.
    Before the wizard exports the Aurelius entities, you can see the generated code if you go to the Preview tab (Figure 8-15). You are not able to modify the code here but only to observe what the wizard will generate. If you wish to add or remove entities, go back to the Mappings tab, make your changes and the code will be updated.
    ../images/481234_1_En_8_Chapter/481234_1_En_8_Fig15_HTML.jpg
    Figure 8-15

    TMS Aurelius Export form (Mappings/Many-Valued Associations)

     

Dictionary

In General Settings tab in the Export wizard, there is a set of options that determine the creation of a property dictionary (Figure 8-16). These options can be confusing as most of the Delphi developers are familiar with the TDictionary class and they expect to see something that uses it.
../images/481234_1_En_8_Chapter/481234_1_En_8_Fig16_HTML.jpg
Figure 8-16

Dictionary options in TMS Aurelius Export form

What this dictionary is in the context of the Data Modeler and Aurelius is a convenient mapping of the properties in the entities to equivalent fields in classes. In this way, instead of referring to the properties in queries and projections by entering hardcoded strings, you can use class properties.

For example, in CallCentre project, we have the function filter, which adds a filter to queries.
...
function TFormMain.filter(const aWeek: TWeeks; const aCriteria: TCriteria):
    TCriteria;
begin
  if aWeek = wWeek4  then
    result:= aCriteria.Add(Linq['Week'] >= 4)
  else
    result:= aCriteria.Add(Linq['Week'] = integer(aWeek)+1);
end;
Using the dictionary, we can make the following adjustment to the function:
...
uses
  ...,
  Entities.Dictionary;
...
function TFormMain.filter(const aWeek: TWeeks; const aCriteria: TCriteria):
    TCriteria;
begin
  if aWeek = wWeek4  then
    result:=aCriteria.Add(Linq[entitiesDic.Call.Week.PropName] > = 4)
  else
    result:= aCriteria.Add(Linq[entitiesDic.Call.Week.PropName] = integer(aWeek)+1);
end;

The benefit we get from this approach is that we no longer worry about making any mistakes when we enter entity properties. Instead, we rely to the entitiesDic to provide the right property name. The downside is that the code may be seen as more convoluted as there are multiple references to a chain of properties. It is, really, on you to see whether and how this can fit in your coding style.

Events

The Aurelius unit the Data Modeler creates can be, directly, used to your applications. There are, however, occasions where some fine-tuning of the code is required. One such occasion is the inheritance strategy we followed when we wanted to introduce the create and modify details in the agent entries. This modification cannot be achieved using the export wizard.

Instead, we need to use another mechanism that is provided by Data Modeler. The application introduces a number of events triggered during the generation of the Aurelius source code. More specifically, at the time of writing, the following events are generated:
  • When a unit is fully created (OnUnitGenerated)

  • When a class (entity) is completely created (OnClassGenerated)

  • When a property and the corresponding field are created in a class (OnColumnGenerated)

  • When an association is created (OnAssociationGenerated)

  • When a many-valued association is created (OnManyValuedAssociationGenerated)

You can find the full details for the events in the manual of Data Modeler. For our inheritance case, we will focus on the OnClassGenerated event. If we refer back to the modifications we did in our code to introduce and configure inheritance, we see that we added and removed some code from the TBase and TAgent classes.

In TBase class , we, only, added the inheritance attribute. Let us do this in Data Modeler.
  1. 1.

    Launch the Aurelius Export wizard and make sure that the Base entity is selected in the Mappings tab (Figure 8-15). You can click the Preview tab and make sure code for TBase appears.

     
  2. 2.

    Switch to the Script tab and click Declare Events button. You should be able to see all the events the wizard supports. We need the OnClassGenerated, so you can delete the rest to keep the script simple.

     
  3. 3.
    Add the following code:
    procedure OnClassGenerated(Args: TClassGeneratedArgs);
    begin
      if Args.CodeType.Name = 'TBase' then
      begin
        Args.CodeType.AddAttribute('Inheritance').
                            AddRawArgument('TInheritanceStrategy.JoinedTables');
      end;
    end;
     
  4. 4.

    You can check that the attribute has been added in TBase entity if you go to the Preview tab.

     

Tip

If you want to check the properties and methods TClassGeneratedArgs (and, for this purpose, other data types in the script), you can use the embedded debugger. In the Script tab, click Debug and, in the debugger, select the ViewLibrary menu. You will be able to explore the whole class tree the scripting engine uses.

For TAgent , we need to declare that the class inherits from TBase and then remove the ID (primary key) field, property, and attribute.
  1. 1.
    In the same procedure in the script, add the following code. This will make TAgent an ancestor of TBase.
    procedure OnClassGenerated(Args: TClassGeneratedArgs);
    begin
      if Args.CodeType.Name = 'TAgent' then
      begin
        // Add the Base class
        Args.CodeType.BaseType:=TCodeTypeReference.Create('TBase');
      end;
      ...
    end;
    Next, we remove the FID field, the ID property, and the Id attribute from the class. The process is the same for all three things and, therefore, I will just show the code.
    procedure OnClassGenerated(Args: TClassGeneratedArgs);
    var
      field: TCodeTypeMember;
      attr: TCodeAttributeDeclaration;
      i: integer;
    begin
      if Args.CodeType.Name = 'TAgent' then
      begin
        ...
        // Remove the ID Field
        for i:=Args.CodeType.Members.Count - 1 downto 0 do
        begin
          field:=Args.CodeType.Members.Items[i];
          if field.Name='FID' then
          begin
            Args.CodeType.Members.Delete(i);
            break;
          end;
        end;
        // Remove the ID property
        for i:=Args.CodeType.Members.Count - 1 downto 0  do
        begin
          field:=Args.CodeType.Members.Items[i];   
          if field.Name='ID' then
          begin
            Args.CodeType.Members.Delete(i);
            break;
          end;
        end;
        // Remove the ID Attribute
        for i:=Args.CodeType.CustomAttributes.Count – 1 downto 0 do
        begin
          attr:=Args.CodeType.CustomAttributes.Items[i];
          if attr.Name='Id' then
          begin
            Args.CodeType.CustomAttributes.Delete(i);
            break;
          end;
        end;
      end;
      ...
    end;

    Note that I iterate through the Members object list twice because removing an item affects the list itself, and I am not sure how sophisticated the script interpreter is. Additionally, the interpreter does not understand the for..in loop notation. You can find the script in the code files (DataModelerScript.pas).

     

Summary

In this chapter, we look at another tool that accompanies Aurelius and can assist developers. TMS Data Modeler provides a way to implement model- and database-first approaches, and it adds value by automating the entity units Aurelius consumes. At the same time, the tool offers a flexible way to customize the final units.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.163.175