My Top 10 Aurelius Features - #6 Legacy Databases

Thursday, January 12, 2017

The video series "My Top 10 Aurelius Features" continues. The #6 feature is the support for Legacy Databases, and the following video shows it in action. English and Portuguese subtitles available!



In my opinion, TMS Aurelius will be very limited if it required a very strict database schema/structure. It would be great for creating prototypes and new applications from scratch, but a no-no for existing applications and databases - and we all know that in Delphi world, what we have most is existing and legacy applications!

That's why Aurelius provides an extensive list of mapping attributes that makes it compatible with virtually any existing database structure you might have. Attributes like

Id
Allows you to use databases with different primary key, like GUID, String, UUID, or even Composite Id's

Column and JoinColumn
Allows you to specify how one table relates to another (foreign keys) even if it doesn't reference a primary key in parent table (an unique index might be used).

Those are just small examples. And the best part is that you don't need to map it yourself. You can use the TMS Data Modeler tool to import an existing database structure and generate all the mapping for you!

Please watch the video for more details and to see TMS Data Modeler source code generation in action.

As usual, don't forget to subscribe to our YouTube channel and receive notifications about upcoming videos!

Wagner R. Landgraf


Bookmarks: 

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



My Top 10 Aurelius Features - #7 Schema Update

Tuesday, January 03, 2017

When creating a database-based application, one of the tasks I always considered boring was to create the database structure, table, fields, foreign keys. Even using a tool to generate a SQL would require me to create a table, add columns, column types, foreign keys, etc.

That's why I consider the ability to automatically create and update the database schema to be the #7 feature of My Top 10 Aurelius Features. It's simply something I don't need to care about anymore.



Updating the database is as easy as doing this:
TDatabaseManager.Update(Connection);
That simple command will check the existing schema in the database and create the missing objects needed to persist all the objects you are dealing with: new tables, columns, foreign keys.

Note that I'm talking about updating, not creating the database. That makes application prototyping and development really fast. Create the database, change your application, add a new table, update the database, and it goes on.

Even though updating the database is as simple as using one line of code, Aurelius provides advanced features for the database update process, like validation. With a code like the following, you can check the differences between the schema of the existing database, and what is needed in the schema to hold the current entity model you have. It shows you the differences without requiring you to actually execute the SQL statements:
procedure TForm1.ValidateMyDatabaseSchema;
var
  DBManager: TDatabaseManager;
  Action: TSchemaAction;
  Warning: TSchemaWarning;
  Error: TSchemaError;
begin
  DBManager := TDatabaseManager.Create(Connection);
  try
    DBManager.ValidateDatabase;

    { Show SQL statements }
    mmStatements.Clear;
    for Statement in DBManager.SQLStatements do
    begin
      mmStatements.Lines.Add(Statement);
      mmStatements.Lines.Add('');
    end;

    { Show validation results }
    mmValidation.Clear;

    mmValidation.Lines.Add('----- Actions -----');
    for Action in DBManager.Actions do
      mmValidation.Lines.Add(Format('%s -> %s', [Action.ClassName, Action.Text]));
    mmValidation.Lines.Add('');

    mmValidation.Lines.Add('----- Warnings -----');
    for Warning in DBManager.Warnings do
      mmValidation.Lines.Add(Format('%s: %s', [Warning.ClassName, Warning.Text]));
    mmValidation.Lines.Add('');

    mmValidation.Lines.Add('----- Errors -----');
    for Error in DBManager.Errors do
      mmValidation.Lines.Add(Format('%s: %s', [Error.ClassName, Error.Text]));
  finally
    DBManager.Free;
  end;
end;
Please watch the video (closed captions available!) to see how schema update and validate in action. As usual, don't forget to subscribe to our YouTube channel and receive notifications about upcoming videos!

Wagner R. Landgraf


Bookmarks: 

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



My Top 10 Aurelius Features - #8 Lazy Loading

Thursday, December 22, 2016

The ability to lazy-load an association is placed at number 8 in the list of My Top 10 Aurelius Features.


Suppose you have a TContact class like the following:
type
  TContact = 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;
And you use the following code to retrieve a list of contacts and get the name of the country of the first contact (to reduce size, the code doesn't check for potential errors or release memory):
// Get all contacts
MyContacts := Manager.Find<TContact>.List;

// Get name of country of first contact:
FirstContactCountryName := MyContacts[0].Country.Name;
By default, when the first line is executed, Aurelius builds and executes an SQL statement retrieving column values from Contact table and Country table, all at once, and all entities (contacts and countries) are created and instantiated:
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 CONTACT A LEFT JOIN COUNTRY B ON (B.ID = A.COUNTRY_ID)
When the second line is executed (when we retrieve the Country name), all data is already in memory. This is useful in many situations, but there might be cases where doing several LEFT JOIN for many associations and retrieving data for all records is not desired. If I'm not going to get the country name for all contacts I retrieve, for example, why would I want to retrieve all country names in advance?

The lazy-loading feature gives you that flexibility. You just reimplement your class using a special Proxy type:
type
  TContact = class
  private
    FId: integer;
    FName: string;
    FCountry: Proxy<TCountry>;
    function GetCountry: TCountry;
    procedure SetCountry(const Value: TCountry);
  public
    property Id: integer read FId write FId;
    property Name: string read FName write FName;
    property Country: TCountry read GetCountry write SetCountry;
  end;

function TContact.GetCountry: TCountry;
begin
  Result := FCountry.Value;
end;

procedure TContact.SetCountry(const Value: TCountry);
begin
  FCountry.Value := Value;
end;
If we execute the code that retrieve contacts again, this is the SQL executed by Aurelius:
SELECT A.ID AS A_ID, A.NAME AS A_NAME, A.COUNTRY_ID AS A_COUNTRY_ID 
FROM CONTACT A
No data from country was retrieved. Only when the second line is executed, Aurelius executes an extra SQL statement:
SELECT A.ID AS A_ID, A.NAME AS A_NAME
FROM COUNTRY A
and then the country object is populated. Now you have the two options and you can fine tune your application for the best situation. It's also worth note that even when you have mapped your class to lazy-load a specific association, in some queries you can force Aurelius to still load all objects at once. The following code will use LEFT JOIN and retrieve all countries in the same SQL statement:
// Get all contacts and countries at once,
// regardless of lazy-load mode in mapping
MyContacts := Manager.Find<TContact>
  .CreateAlias('Country', 'c', TFetchMode.Eager)
  .List;

// Get name of country of first contact:
FirstContactCountryName := MyContacts[0].Country.Name;
Don't forget to subscribe to our YouTube channel and receive notifications about upcoming videos!


Wagner R. Landgraf


Bookmarks: 

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



My Top 10 Aurelius Features - #9 Plain Old Delphi Objects

Monday, December 12, 2016

My number 9 feature of My Top 10 Aurelius Features refers to PODO - Plain Old Delphi Objects. Or in other words, the fact that Aurelius entities are simple, clean Delphi classes.

When Aurelius project started back in mid 2011, there were some object persistent frameworks available for Delphi. At that time I was wondering why such existing ones did not catch up strongly.

There were some people using it, but I didn't feel it was something that people were embracing. And one of the reasons (in my opinion) was that to make classes to become persistent the developer needed to do some many workarounds that the class end up being cluttered with unneeded things, and coding with that class was not very pleasant.

Imagine writing business code with the class below:
type
  TContact = class(TORMBaseClass)
  private
    FId: TORMInteger;
    FName: TORMString;
    FCountry: TORMRelationship;
    function GetId: TORMInteger;
    procedure SetId(const Value: TORMInteger);
    function GetName: TORMString;
    procedure SetName(const Value: TORMName);
    function GetCountry: TORMRelationship;
    procedure SetCountry(const Value: TORMRelationship;
  public
    constructor Create(SpecificParams: TORMINit); override;
    destructor Destroy;
    procedure Save(Context: TORMContext); override;
    procedure Update(Context: TORMContext); override;
    procedure Delete(Context: TORMContext); override;
  published
    property Id: TORMInteger read GetId write SetId;
    property Name: TORMString read GetName write SetName;
    property Country: TORMRelationship read GetCountry write SetCountry;
  end;

And compare to using this class when writing your code:
type
  TContact = 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;

The first code is not specific to any other persistent framework. Actually, it's really fake, it's just a combination of ideas and solutions used by several frameworks I've seen over the years. The fact that you have to inherit from a base class, override base methods in it, have a constructor implemented that initialize mappings manually, use framework-specific types instead of primitive Delphi types, and the list goes on.

Finally, having clean, pure Delphi classes allow you to extend them, like creating non-persistent properties, using pure object references to represent associations, etc. The following video gives slightly more details about that.




Wagner R. Landgraf


Bookmarks: 

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



My Top 10 Aurelius Features - #10 Automapping

Monday, December 05, 2016

This is the #10 feature of My Top 10 Aurelius Features. Follow the link for the full list!

Using the automapping feature is easy: you just need to add the [Automapping] attribute to your class:
type
  [Entity, Automapping]
  TCustomer = class
  private
    FId: Integer;
    FName: string;
  public
    property Id: Integer read FId write FId;
    property Name: string read FName write Name;
  end;

and it will automatically map the class to the database using some conventions. This prevents you from having to manually use the mapping attributes like Column, Table, Id, etc., to tell Aurelius exactly how the database schema will be.

If you already have an existing database, this is not very useful since the convention used by it will probably not match your existing database. But when you do the code-first approach (my favorite) - where you start your application creating the classes, not the tables - this is very handy.

Besides the obvious fact that Automapping speeds up the development process (prototyping is really fast with it), what I really like about Automapping is how it makes you forget about the database and think only about classes: you just don't remember you have a table or a column somewhere in the database - let Aurelius chooses whatever it needs for the underlying database, and focus on your code.

Another interesting thing is that it's not an all-or-nothing feature: you can have use the Automapping attribute but still override it with specific mapping attributes - so you get the best of both worlds: convention with flexibility:
type
  [Entity, Automapping]
  TCustomer = class
  private
    FId: Integer;
    [Column('CUSTOMER_NAME', [TColumnProp.Required], 120)]
    FName: string;
  public
    property Id: Integer read FId write FId;
    property Name: string read FName write Name;
  end;

And to conclude, here is a small video (~4 min) showing Automapping usage:



Wagner R. Landgraf


Bookmarks: 

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




Previous  |  Next  |  Index