PRODUCTS » Quick Links

FEATURED PRODUCT

Set of VCL components to offer easy access from Windows applications to cloud services

License only 95 EUR See More

LOGIN

Customer login to access products, support information & special benefits.

SEARCH

STAY IN TOUCH

Add your e-mail address here to receive the monthly TMS Software alerts.

 


Content Filter:
Product releases
Product articles
Technical articles
Website changes
Software development

Product Filter:

<< >>
May 2013





Friday, May 10, 2013

TMS Aurelius in your iPhone

We have just released TMS Aurelius 2.1 with XE4 support. This "small" release took a little longer, but with a good reason for that: thanks to the new iOS compiler provided in Delphi XE4, now TMS Aurelius supports iOS devices, in addition to the already supported Win32, Win64 and OS X platforms!

As you might already know, the new iOS compiler has some different concepts than the traditional Win32 compiler we are used to. Automatic reference counting for objects and zero-based strings are the main ones, and also the fact that pointers usage is discouraged now.

But for those considering using this new iOS compiler, there is good news. Personally, I was surprised, in a positive way, how backward compatible it is. Of course it depends on your code. If it has heavy pointer usage, lots of low-level hacks, etc., you might have a lot of work to do. But other than this, there is a good chance that you code will work smoothly on iOS. I can speak for Aurelius. It can be considered a very new TMS product (a little more than one year passed since 1.0 release in January, 2012) so it uses several new language features like generics, new RTTI, among other recent additions that helps the code to be very clean, well structured and with almost no pointer usage. Making most of it to compile to iOS required minimum changes, and it worked fairly well (of course, all our tests passed, in both iOS simulator and iOS device).

I said it was easy to compile "most of it" because the only exception was TAureliusDataset. Not that it was a nightmare, but without it, the other parts of TMS Aurelius would be compiling and running on iOS in a matter of minutes. But TAureliusDatset of course descends from TDataset which is a code that heavily uses pointers, internal buffers, etc.. So it required a some effort to convert.

All in all, you can have your TMS Aurelius code working on iOS, with all existing features, including TAureliusDataset and native SQLite support. And the best part is that you can use it the same way you do in Delphi: since TMS Aurelius already manages the memory in VCL/FMX applications (you usually don't have to worry about destroying objects retrieved from the database), you will have the same behavior in iOS.

Bookmarks: 

Wagner Landgraf




This blog post has received 4 comments. Add a comment.



Friday, March 22, 2013

Visual Data Binding using TAureliusDataset

When dealing with Aurelius and any ORM framework, one common task is to build a graphical user interface to edit/display the data. Delphi users are used to the TDataset component, which not only retrieves data from the database but also act as middle layer between the data and visual controls. When using Aurelius, you don't use any TDataset descendant to directly retrieve data - all business data are objects that are retrieved by Aurelius itself.

To bind your objects to visual controls, you could use the new Visual Live Bindings feature. But Aurelius also provides an additional way of doing that - you can use TAureliusDataset, a TDataset descendant which behaves as any other TDataset - the only difference is that entity objects are the "data" for this dataset.

Consider the following code:
var
  Customers: TList<TCustomer>;
  Dataset: TAureliusDataset;
{...}
  Customers := Manager.Find<TCustomer>.List;
  Dataset.SetSourceList(Customers);
  Dataset.Open;
The first line retrieves a list of TCustomer objects from the database. The second line tells the dataset that its data is coming from Customers list, and then third line just opens the dataset. Now if we want to display our data in a TDBGrid control, for example, we just do it the way we are used to: link the grid to a TDatasource, then link the datasource to the TAureliusDataset. Your customers will be displayed in the grid.

TAureliusDataset automatically maps each property to a field in dataset. So if your customer is declared like this:
type
  TCustomer = class
  {...}
    property CustName: string read FCustName write FCustName;
you will be able to read/write the property using this code:
CurrentName := Dataset.FieldByName('CustName').AsString;
Dataset.Edit;
Dataset.FieldByName('CustName').AsString := CurrentName + ' - sufix';
Dataset.Post;
You could also edit a single object directly, without needing to retrieve a list. This will also work if you just want to edit properties of a single object:
SpecificCustomer := Manager.Find<TCustomer>(CustomerId);
Dataset.SetSourceObject(SpecificCustomer);
Dataset.Open;
Some could say that you could use the new Visual Live Bindings. Yes, of course, you can. This is just another option, with pros and cons. But it has some interesting/different things:

1. You can use existing data-aware controls. Delphi is now 18 year-old. There are numerous existing controls that support TDataset, but not live bindings. Data-aware grids, planners, controls, etc.. All of those can be used and be bound to the objects.

2. TDataset provides a temporary cache/buffer. This means that until you effectively Post, objects are not changed. Remember this acts as a TDataset. While the dataset is being edited and field contents are updated, only the internal dataset buffer is updated. Data is effectively saved in the objects (the "data") only after Post. This gives you great flexibility when you need to build user interfaces where user can cancel changes, or only update data when clicking "Ok". If you use live bindings, you would have to do something else to achieve such behavior.

Not only that, TAureliusDataset is not just a property->field mapper. It's really powerful. Here is a list of many things TAureliusDataset can do and features it supports (I might write about these in a future post):

  • Fetch-on-demand (will talk about this in a future post)
  • Offline, paged fetch-on-demand (same as above)
  • Sub-properties (properties of associated entities)
  • Entity fields (fields representing an association)
  • Dataset fields (master-detail)
  • Supports inheritance/polymorphism (list of objects of different classes)
  • Enumerated types
  • Lookup fields
  • Locate/Lookup methods
  • Filtered data
  • Calculated fields
  • Design-time support
To conclude, here are two screenshots that illustrate how you can use TAureliusDataset at design-time:








Bookmarks: 

Wagner R. Landgraf




This blog post has not received any comments yet. Add a comment.



Friday, March 08, 2013

Crash Course TMS Aurelius – Inheritance and Polymorphism

Inheritance is one of my favorite features in Aurelius. One of benefits of using an ORM is abstracting the SQL and start thinking (almost) purely in OOP. Inheritance and polymorphism are fundamental features of Object-oriented programming, and if when designing your model you can't use it, then the "object-relational" mapping would just become a simple "property>column" mapping in the end.

Aurelius allows you to build a class hierarchy that can be persisted, and provides you with two strategies to persist it: joined tables and single table. The former will create a different table for each class and add the proper relationships, and the later will save the whole hierarchy in the same table. You can learn more about it reading the topic "Inheritance Strategies" in documentation.

Let me illustrate how it works. Considering the following classes and mapping:
type
  [Entity, Automapping]
  [Inheritance(TInheritanceStrategy.JoinedTables)]
  TPerson = class
  private
    FId: integer;
    FName: string;
  public
    property Id: integer read FId write FId;
    property Name: string read FName write FName;
  end;

  [Entity, Automapping]
  TEmployee = class(TPerson)
  private
    FSalary: Currency;
  public
    property Salary: Currency read FSalary write FSalary;
  end;
Note that mapping is also very straightforward, all you need to do is specify the strategy to be used in the base class of your hierarchy. Now you can save your objects in the same way we did in previous posts:
function SavePerson(Manager: TObjectManager): integer;
var
  Person: TPerson;
begin
  Person := TPerson.Create;
  Person.Name := 'John Person';
  Manager.Save(Person);
  Result := Person.Id;
end;

function SaveEmployee(Manager: TObjectManager): integer;
var
  Employee: TEmployee;
begin
  Employee := TEmployee.Create;
  Employee.Name := 'James Employee';
  Employee.Salary := 1999.99;
  Manager.Save(Employee);
  Result := Employee.Id;
end;
After calling the above methods, we have one TPerson object and one TEmployee object persisted in the database. We can use the following code to retrieve them using the generated id's:
procedure OutputPerson(Person: TPerson);
begin
  if Person <> nil then
    WriteLn(Format('Class: %s; Name: %s', [Person.ClassName, Person.Name]))
  else
    WriteLn('nil');
end;

procedure OutputEmployee(Employee: TEmployee);
begin
  if Employee <> nil then
    WriteLn(Format('Class: %s; Name: %s; Salary: %s',
      [Employee.ClassName, Employee.Name, FloatToStr(Employee.Salary)]))
  else
    WriteLn('nil');
end;

procedure CheckPersonAndEmployee(Manager: TObjectManager; PersonId, EmployeeId: integer);
var
  Person: TPerson;
  Employee: TEmployee;
begin
  Person := Manager.Find<TPerson>(PersonId);
  OutputPerson(Person);
  Person := Manager.Find<TPerson>(EmployeeId);
  OutputPerson(Person);
  Employee := Manager.Find<TEmployee>(EmployeeId);
  OutputEmployee(Employee);
  Employee := Manager.Find<TEmployee>(PersonId);
  OutputEmployee(Employee);
end;
This is what we get as the output:
Class: TPerson; Name: John Person
Class: TEmployee; Name: James Employee
Class: TEmployee; Name: James Employee; Salary: 1999.99
nil
Now you see polymorphism in action. The first two Find calls ask for a TPerson object. It happens that the first id is a TPerson object indeed, but the second is an id for a TEmployee object. Both are retrieved because a TEmployee is a TPerson. Also note that the retrieved object in second Find is actually a TEmployee object.

The last two Find calls ask for a TEmployee object. When the EmployeeId is provided, the correct TEmployee object is retrieved. But when we ask for a TEmployee object passing PersonId as Id, nil is returned - although the object is in database with that id, it's not returned because the object is not a TEmployee, but only a TPerson.

As in the previous posts, I will provide here some SQL statements generated by Aurelius, for a better understanding. When using joined tables strategy, Aurelius will create the following database structure (SQL Server syntax):
CREATE TABLE PERSON (
  ID INTEGER IDENTITY(1,1) NOT NULL,
  NAME VARCHAR(255) NOT NULL,
  CONSTRAINT PK_PERSON PRIMARY KEY (ID));

CREATE TABLE EMPLOYEE (
  ID INTEGER NOT NULL,
  SALARY NUMERIC(20, 4) NOT NULL,
  CONSTRAINT PK_EMPLOYEE PRIMARY KEY (ID));

ALTER TABLE EMPLOYEE ADD CONSTRAINT 
  FK_EMPLOYEE_PERSON_ID FOREIGN KEY (ID) REFERENCES PERSON (ID);
Each class will have its data saved in a different database, and retrieving a TEmployee object would execute the following statement:
SELECT A.ID AS A_ID, A.SALARY AS A_SALARY, B.ID AS B_ID, B.NAME AS B_NAME
FROM EMPLOYEE A
  LEFT JOIN PERSON B ON (B.ID = A.ID)
WHERE  B.ID = :p_0
To conclude this post, let's change the strategy to single table. This will make the mapping look like this (Salary property has to be nullable because all data will stay in a single table):
  [Entity, Automapping]
  [Inheritance(TInheritanceStrategy.SingleTable)]
  [DiscriminatorColumn('PERSON_TYPE', TDiscriminatorType.dtString)]
  [DiscriminatorValue('Person')]
  TPerson = class
  private
    FId: integer;
    FName: string;
  public
    property Id: integer read FId write FId;
    property Name: string read FName write FName;
  end;

  [Entity, Automapping]
  [DiscriminatorValue('Employee')]
  TEmployee = class(TPerson)
  private
    FSalary: Nullable<Currency>;
  public
    property Salary: Nullable read FSalary write FSalary;
  end;
Code will be exactly the same. Database structure will become just this:
CREATE TABLE PERSON (
  ID INTEGER IDENTITY(1,1) NOT NULL,
  NAME VARCHAR(255) NOT NULL,
  PERSON_TYPE VARCHAR(30) NOT NULL,
  SALARY NUMERIC(20, 4) NULL,
  CONSTRAINT PK_PERSON PRIMARY KEY (ID));
and this is how an employee is retrieved from database:
SELECT A.ID AS A_ID, A.NAME AS A_NAME, A.PERSON_TYPE AS A_PERSON_TYPE, A.SALARY AS A_SALARY
FROM PERSON A
WHERE A.PERSON_TYPE = :p_1
 AND A.ID = :p_0

p_0 = "1" (ftInteger)
p_1 = "Employee" (ftString)


Bookmarks: 

Wagner Landgraf




This blog post has not received any comments yet. Add a comment.



Thursday, February 28, 2013

Crash Course TMS Aurelius – Blobs

Using blobs in Aurelius is very straightforward and yet very powerful. In summary, all you have to do is declare your field/property as TBlob (declared in unit Aurelius.Types.Blob.pas). This is enough to map it to an existing blob field in your table, and you will be able to save/load the blob content is several many ways. Consider the following mapping:
  [Entity, Automapping]
  TCustomer = class
  private
    FId: integer;
    FName: string;
    FDocument: TBlob;
    [Column('Photo', [TColumnProp.Lazy])]
    FPhoto: TBlob;
    [Column('Descr_Field', [], 65536)]
    FDescription: string;
  public
    property Id: integer read FId write FId;
    property Name: string read FName write FName;
    property Document: TBlob read FDocument write FDocument;
    property Photo: TBlob read FPhoto write FPhoto;
    property Description: string read FDescription write FDescription;
  end;
We have declared three blob properties in our class: Document, Photo and Description. I have used those to show slightly different ways of using blobs. Document and Photo are declared as TBlob, which is the recommended way. Alternatively, Description is declared as string, but manually mapped to database using a size greater than 65535. This tells Aurelius to also consider this field as a blob (memo/cblob to be more specific) instead of VarChar. You could also declare the property as a dynamic array of byte, but it's not recommended, since you gain nothing from doing it. Note that I have also used an unusual field name for Description (Descr_Field) just to show you how manual mapping works.

There is another interesting feature about blobs: Photo is declared as lazy (TColumnProp.Lazy). This indicates that Aurelius will not bring the blob from database when Customer data is retrieved. The blob is only retrieved when your code explicitly reads the content of Photo property.

The following code shows different ways of dealing with blobs (saving and loading):
function SaveCustomerWithBlobs(Manager: TObjectManager): integer;
var
  Customer: TCustomer;
begin
  Customer := TCustomer.Create;
  Customer.Name := 'John';

  Customer.Photo := TFile.ReadAllBytes('picture.bmp');
  Customer.Document.AsBytes := TFile.ReadAllBytes('document.pdf');
  Customer.Description := TFile.ReadAllText('description.txt');

  Manager.Save(Customer);
  Result := Customer.Id;
end;

procedure LoadCustomerAndExportBlobs(Manager: TObjectManager; CustomerId: integer);
var
  Customer: TCustomer;
begin
  Customer := Manager.Find<TCustomer>(CustomerId);

  TFile.WriteAllText('description2.txt', Customer.Description);
  TFile.WriteAllBytes('document2.pdf', Customer.Document);
  TFile.WriteAllBytes('picture2.bmp', Customer.Photo.AsBytes);
end;
As you can see, you can use TBlob.AsBytes property explicitly, or rely on the implicit conversion to TBytes. You could also use AsString property and streams:
  Customer.Photo := TBlob.Create(TFile.ReadAllBytes('picture.bmp'));
  Customer.Document.AsString := 'Some document';
  Stream := TFile.Open('picture.bmp', TFileMode.fmOpen);
  Customer.Photo.LoadFromStream(Stream);
  Stream.Free;
TBlob type also offers methods like IsNull, Clear, and also provides direct access to raw data to improve performance if needed.

As a final note: we have added TColumnProp.Lazy to the Photo blob. We can verify if the blob is loaded using the Loaded property. We can change LoadCustomerAndExportBlobs function to check it:
  Customer := Manager.Find<TCustomer>(CustomerId);

  Assert(Customer.Document.Loaded);
  TFile.WriteAllBytes('document2.pdf', Customer.Document);

  Assert(not Customer.Photo.Loaded);
  TFile.WriteAllBytes('picture2.bmp', Customer.Photo.AsBytes);
  Assert(Customer.Photo.Loaded);
After TCustomer instance is loaded, Document property is loaded, but Photo is not loaded, because we set it as lazy. After we access content of Photo to save it to picture2.bmp file, Photo.Loaded becomes true. You can also see it happening when you check the generate SQL statements:
CREATE TABLE CUSTOMER (
  ID INTEGER NOT NULL,
  NAME VARCHAR(255) NOT NULL,
  DOCUMENT BLOB,
  Photo BLOB,
  Descr_Field BLOB SUB_TYPE TEXT,
  CONSTRAINT PK_CUSTOMER PRIMARY KEY (ID));

CREATE GENERATOR SEQ_CUSTOMER;

SELECT GEN_ID(SEQ_CUSTOMER, 1)
FROM RDB$DATABASE;

INSERT INTO CUSTOMER (
  ID, NAME, DOCUMENT, Photo, Descr_Field)
VALUES (
  :A_ID, :A_NAME, :A_DOCUMENT, :A_Photo, :A_Descr_Field);

SELECT A.ID AS A_ID, A.NAME AS A_NAME, A.DOCUMENT AS A_DOCUMENT, A.Descr_Field AS A_Descr_Field
FROM CUSTOMER A
WHERE  A.ID = :p_0;

SELECT A.Photo As f0_
FROM CUSTOMER A
WHERE  A.ID = :p_0;
The first statement shows how the table is created (Firebird in this example). The INSERT statement creates the customer in the database, and saves the blobs. The first SELECT statement retrieves only Document and Description, but not Photo. The last SELECT statement is executed later, to retrieve the content of Photo field, only when the content of Photo property is read from code.

Bookmarks: 

Wagner Landgraf




This blog post has not received any comments yet. Add a comment.



Monday, February 18, 2013

Crash Course TMS Aurelius – Associations (Foreign Keys)

Besides mapping tables to classes and table columns to fields/properties, Aurelius also maps relationships (foreign keys) to object associations. One nice thing about Aurelius is that such associations are defined in a very simple way: just references to other objects. Consider the following classes with respective mapping:
type
  [Entity, Automapping]
  TCountry = class
  private
    FId: integer;
    FName: string;
  public
    property Id: integer read FId write FId;
    property Name: string read FName write FName;
  end;

  [Entity, Automapping]
  TCustomer = class
  private
    FId: integer;
    FName: string;
    FCountry: TCountry;
  public
    property Id: integer read FId write FId;
    property Name: string read FName write FName;
    property Country: TCountry read FCountry write FCountry;
  end;
Note that TCustomer has an association to TCountry, meaning that every customer has a country associated to it. The following code should how you would save a TCustomer object with an associated TCountry object:
function CreateCustomerWithCountry(Manager: TObjectManager): integer;
var
  Customer: TCustomer;
  USACountry: TCountry;
begin
  USACountry := TCountry.Create;
  USACountry.Name := 'USA';
  Customer := TCustomer.Create;
  Customer.Name := 'John';
  Customer.Country := USACountry;
  Manager.Save(Customer);
  Result := Customer.Id;
end;
Very simple and straightforward. Note that we didn't need to save Country object - when Customer is saved, Country is automatically saved because it's associated to it (this is the default behavior of automapping. You can fully configure the mapping to avoid Country to be saved automatically, if you want to).

It's also very simple to retrieve an object and its associations from database. Consider the following code that takes a customer id and returns the name of the country associated with the customer:
function GetCountryNameFromCustomer(Manager: TObjectManager; CustomerId: integer): string;
var
  Customer: TCustomer;
begin
  Customer := Manager.Find<TCustomer>(CustomerId);
  if Customer <> nil then
    Result := Customer.Country.Name
  else
    Result := '';
end;
The code retrieves a TCustomer object instance based on the id (such Find method will be topic for another blog post). To obtain the name of the country, all we have to do is to get the associated TCountry object instance (through the TCustomer.Country property) and return its Name property. Also very simple. When TCustomer instance was retrieved, its associated objects were also retrieved. You can also fully configure this, and you can even set up things so that the TCountry object is only retrieved when needed (also a topic for another post).

Associations are a core feature of any ORM framework and this small example is a very simple one. Aurelius has many features related to associations, many ways of dealing with them, saving, retrieving, etc. But the purpose of this blog post is just to explain the concept. Feel free to ask questions in comment about what else you would like to be better explained in a future blog post.

To make it even more clear, I will post here the SQL statements executed by Aurelius when the code above was executed, so you can easily relate the objects with the underlying database. The statements used here were executed in an SQL Server database (syntax will be different if using another database).

The following statements were executed to create the tables so you can have an idea of the database structure (code to create the database is not explicit in this post):
CREATE TABLE COUNTRY (
  ID INTEGER IDENTITY(1,1) NOT NULL,
  NAME VARCHAR(255) NOT NULL,
  CONSTRAINT PK_COUNTRY PRIMARY KEY (ID));

CREATE TABLE CUSTOMER (
  ID INTEGER IDENTITY(1,1) NOT NULL,
  NAME VARCHAR(255) NOT NULL,
  COUNTRY_ID INTEGER NULL,
  CONSTRAINT PK_CUSTOMER PRIMARY KEY (ID));

ALTER TABLE CUSTOMER ADD CONSTRAINT 
  FK_CUSTOMER_COUNTRY_COUNTRY_ID FOREIGN KEY (COUNTRY_ID) REFERENCES COUNTRY (ID)
When saving the TCustomer object instance (function CreateCustomerWithCountry), the following statements were executed (the content of parameters is displayed):
INSERT INTO COUNTRY (NAME) VALUES (:A_NAME);
A_NAME = "USA" (ftString)

SELECT CAST(IDENT_CURRENT('COUNTRY') AS INT);

INSERT INTO CUSTOMER (
  NAME, COUNTRY_ID)
VALUES (
  :A_NAME, :A_COUNTRY_ID);

A_NAME = "John" (ftString)
A_COUNTRY_ID = "1" (ftInteger)

SELECT CAST(IDENT_CURRENT('CUSTOMER') AS INT)
Finally, and the most interested one in my opinion, this is the SQL statement executed to retrieve back the TCustomer obejct instance. Note that in this example two different TObjectManager objects were used to force the SELECT execution. If a single manager had been used, manager would have retrieved the object directly from manager and would not need to execute an extra SELECT statement to retrieve the data.
SELECT A.ID AS A_ID, A.NAME AS A_NAME, A.COUNTRY_ID AS A_COUNTRY_ID, B.ID AS B_ID, B.NAME AS B_NAME
FROM CUSTOMER A
  LEFT JOIN COUNTRY B ON (B.ID = A.COUNTRY_ID)
WHERE  A.ID = :p_0

p_0 = "1" (ftInteger)


Bookmarks: 

Wagner Landgraf




This blog post has not received any comments yet. Add a comment.




Previous  |  Next  |  Index

Copyright © 1995 - 2013 TMS Software v3.5