TDatabaseManager.Create() and Rtti problem - (2) a

TDatabaseManager.Create() and Rtti problem - (2) a suggestion

It looks like Rtti only knows types when instances of this types appear explicitly in the code.
In order to force Rtti to "discover" entities, here is my proposition:

* declare somewhere in Aurelius code - in interface section of Aurelius.Mapping.MappedClasses, for example - a procedure called RegisterEntity(clazz: TClass).
* implement this procedure with an empty body (it does nothing special):
    procedure RegisterEntity(Clazz: TClass);
    begin
    end;

* in the delphi unit implementing the entities,
    - add Aurelius.Mapping.MappedClasses to the uses clause;
    - create an initialization section at the end of the unit containing:
    initialization
      RegisterEntity(<entity class name>);  

Of course, better solutions are welcome.
Registering an entity is only needed when the program never makes use of instances of this entity (for example, when the only purpose of the software is to build a database schema).


************************* Current message follows this one:
Try this.
Create an entity
 

unit uEntity;

interface

uses Aurelius.Mapping.Attributes;

type

  [Entity]
  [Table('DEPARTMENT')]
  [Sequence('SEQ_DEPARTMENT')]
  [Id('FId', TIdGenerator.IdentityOrSequence)]
  TDepartment = class
  private
    [Column('ID', [TColumnProp.Unique, TColumnProp.Required, TColumnProp.NoUpdate])]
    FId: Integer;
    [Column('DEPARTMENT_NAME', [TColumnProp.Required], 50)]
    FDepartment_Name: string;
  public
    property Id: Integer read FId write FId;
    property Department_Name: string read FDepartment_Name write FDepartment_Name;
  end;


implementation

end.


and create a  Delphi form only dedicated to build the database objects:


procedure TfrmMappedClasses.actCreateExecute(Sender: TObject);
var
  DBManager: TDatabaseManager;
  sMsg: string;
begin
  DBManager := TDatabaseManager.Create(GetDBConnection);
  try
    DBManager.BuildDatabase;
    DBManager.Free;
  except
    on E: Exception do
    begin
      DBManager.Free;
      MessageDlg(E.Message, mtError, [mbOK], 0);
      exit;
    end;
  end;
end;
**************************

where:
    - actCreate action is called from a button;
    - GetDBConnection method creates the IDbConnection instance.
IMPORTANT:
    uEntity exists in the uses clause of the form;
    the private and public sections of TfrmMappedClasses class declaration must not contain members of type TDepartment.

When actCreate action is executed, nothing happens (table DEPARTMENT and sequence SEQ_DEPARTMENT are not created).
When you add an useless
    department: TDepartment
member to the private or public section of class TfrmMappedClasses, the database objects are created.


If you inspect Aurelius (version 1.4) Aurelius.Mapping.MappedClasses unit, you notice that gathering Entity classes is achieved through an Rtti context:

procedure TMappedClasses.GetEntityClasses(AList: TList<TClass>);
var
  Context: TRttiContext;
  AllTypes: TArray<TRttiType>;
  T: TRttiType;
  A: TCustomAttribute;
begin
  Context := TRttiContext.Create;
  try
    AllTypes := Context.GetTypes;
    for T in AllTypes do
      if T.IsInstance then
        for A in T.GetAttributes do
          if A is Entity then
            AList.Add(T.AsInstance.MetaclassType);
  finally
    Context.Free;
  end;
end;

We can debug this code with CodeSite:

...
  try
    AllTypes := Context.GetTypes;
    {$IFDEF CODESITE}
    for T in AllTypes do
    begin
      CodeSite.Send(T.Name);
    end;
    {$ENDIF}
    for T in AllTypes do
...

It appears that Context.GetTypes contains TDepartment if and only if class TfrmMappedClasses (the form class) has a TDepartment member (private or public).

How to solve this problem?

    

Hi Etienne,


I see in your other post that you found the way to solve this.
I would like to mention that this is due to Delphi linker, and it makes sense. Since Aurelius gets the classes via RTTI, if the linker removes the classes, there is no way Aurelius can know about it.
And yes, you can just use the class in your application or just register it using TMappedClasses. The reason why TMappedClasses is not documented yet in manual is because it might change in future. So be aware that maybe your registration code will break in a future version. But it's not much code and it will raise a compile error if it does.

Hi,

I encountered a similar problem.



I have source like this



        try

           DM := TDatabaseManager.Create(Connection);

           DM.BuildDatabase;

        finally

           DM.Free;

        end;



        OM := TObjectManager.Create(Connection);

        try

        P := TPersonal.Create;

        P.Free;

      finally

            OM.Free;

        end;



and some entity classes in uses. If I remove the second block of code (where TObjectManager), creating a db schema does not occur (as if I have no entity classes). I spent three nights to find it. This is a bug or something else?



(p.s. sorry for my english)

That's exactly the same "problem". If TPersonal class is not used in your application, linker removes it and Aurelius cannot know about it. You must keep the code, or just write a line like this:

TPersonal.Create.Free;
 
We didn't allow a way to register the class yet because the above workaround is simple and works (and will always be), and we still didn't make the registration classes in its final form, so it might change in future.

Wagner, thanks for the answer. It was the only bad moment. Otherwise I am very pleased with the Aurelius and i'm waiting for 'schema update' feature :)

Simpler solution:
Base your object classes on TPersistent rather than TObject. Then in an initialization section, use the normal RegisterClasses({...]) call.
I am using this approach to build a simple tool to compare database structure with Aurelius model allowing updating / rejectinbg a malformed database.