Use Aurelius Events in a XData Server

Hello,

I'm trying to implement Aurelius events in my XData server because i want to log every insert\update\delete that is performed on the client side. I took as example the MusicLibrary demo and I used my Data Module to implements the various handler for every event. I also subscribe them when the Data Module is created, but when, for example, an update is performed by client side, it isn't thrown any event. I've checked if any event is registered in the ObjectManager's explorer, but i cannot find anything. What am I doing wrong? Is there anything else i have to do? 
Thank you

Hi Stefano,

can you please post the code you are using to implement events and the code you use to call the server that you say isn't working.
This is the definition of the Data Module:

TMyDataModule = class(TDataModule)
    FDConnection1: TFDConnection;
  private
    { Private declarations }
    FInsertedProc: TInsertedProc;
    FDeletedProc: TDeletedProc;
    FUpdatedProc: TUpdatedProc;
    FCollectionItemAddedProc: TCollectionItemAddedProc;
    FCollectionItemRemovedProc: TCollectionItemRemovedProc;
    procedure InsertedHandler(Args: TInsertedArgs);
    procedure DeletedHandler(Args: TDeletedArgs);
    procedure UpdatedHandler(Args: TUpdatedArgs);
    procedure CollectionItemAddedHandler(Args: TCollectionItemAddedArgs);
    procedure CollectionItemRemovedHandler(Args: TCollectionItemRemovedArgs);
  public
    { Public declarations }
    procedure Log(const S: string);
    procedure BreakLine;
    function EntityDesc(Entity: TObject; Manager: TObject): string;
    function PropertiesDesc(Entity: TObject; Manager: TObject): string;
    procedure SubscribeListeners;
    procedure UnsubscribeListeners;
    constructor create(Owner: TComponent); override;
  end;

constructor TMyDataModule.create(Owner: TComponent);
begin
  inherited;
  FInsertedProc:= InsertedHandler;
  FDeletedProc:= DeletedHandler;
  FUpdatedProc:= UpdatedHandler;
  FCollectionItemAddedProc:= CollectionItemAddedHandler;
  FCollectionItemRemovedProc:= CollectionItemRemovedHandler;
  SubscribeListeners;
end;

procedure TMyDataModule.SubscribeListeners;
var
  E: TManagerEvents;
begin
  E := TMappingExplorer.Default.Events;
  E.OnInserted.Subscribe(FInsertedProc);
  E.OnUpdated.Subscribe(FUpdatedProc);
  E.OnDeleted.Subscribe(FDeletedProc);
  E.OnCollectionItemAdded.Subscribe(FCollectionItemAddedProc);
  E.OnCollectionItemRemoved.Subscribe(FCollectionItemRemovedProc);

end;

Here's the procedure that starts the server:

procedure TForm1.actStartExecute(Sender: TObject);
var
  LModule: TXDataServerModule;
begin
  FServer:= THttpSysServer.Create;
  FModule:= TXDataServerModule.Create('http://test:24800/testServer/' +
     'Chiamate',TDBConnectionPooler.CreateConnectionPool,TXDataAureliusModel.Get(
        'Chiamate'));
//  LModule.UserName:='Prova';
//  LModule.Password:='Prova';
  FServer.AddModule(FModule);
  FServer.Start;
  FActive:= True;
end;

class function TDBConnectionPooler.CreateConnectionPool: IDBConnectionPool;
var
  ConnectionPool: IDBConnectionPool;
begin
  ConnectionPool:= TDBConnectionPool.Create(50,
  function : IDBConnection
  var
    MyDataModule: TMyDataModule;
  begin
    MyDataModule:= TMyDatamodule.Create(Application);
    Result:= TFireDacConnectionAdapter.create(MyDataModule.FDConnection1,
       MyDataModule);
  end
  );
  result:= ConnectionPool;
end;

and here's the client procedure where I send a request to the server

procedure TRestClient.execute(AObject: TObject);
begin
  case FRestMethod of
    rmPOST:
      FXDataClient.Post(AObject);
    rmPUT:
      FXDataClient.Put(AObject);
    rmDELETE:
      FXDataClient.Delete(AObject);
  end;
end;

The "problem" here is that you are creating a XData server using model "Chiamate". Thus it will use the mapping explorer associated with that model.

When subscribing to events, use TMappingExplorer.Get('Chiamate') instead of TMappingExplorer.Default.

Thank you very much, I wondered it was something like this, but I couldn't find it

Now I found another problem: when I perform an update, when the ObjectManager prepares everything to handle the this operation, it gets the Entry performing FObjects.getEntry(Entity) on row 1165 of Aurelius.Engine.ObjectManager, but when it starts the for loop in row 1170, Entry.OldState is set as null and it's thrown an Access Violation Exception. My classes are these:


[Entity]
  [Model('Chiamate')]
  [Table('ChiamataDett')]
  [Sequence('SEQ_CHIAMATA_ID')]
  [Id('FID', TIdGenerator.IdentityOrSequence)]
  TChiamata = class
  private
    [Column('ID', [TColumnProp.Required])]
    FID: integer;
    
    [Association([TAssociationProp.Required], CascadeTypeAll - [TCascadeType.Remove])]
    [JoinColumn('IDTipologia', [TColumnProp.Required], 'ID')]
    FTipoChiamata: TTipoChiamata;

    [Column('GUID', [TColumnProp.Required])]
    FGUID: string;

    [Column('TS', [TColumnProp.Required])]
    FTS: TDateTime;

    [Column('DataOra', [TColumnProp.Required])]
    FDataOra: TDateTime;
  public
    property ID: integer read FID write FID;
    property TipoChiamata: TTipoChiamata read FTipoChiamata write FTipoChiamata;
    property GUID: string read FGUID write FGUID;
    property TS: TDateTime read FTS write FTS;
    property DataOra: TDateTime read FDataOra write FDataOra;
  end;

[Entity]
  [Model('Chiamate')]
  [Table('ChiamataDett')]
  [Sequence('SEQ_CHIAMATA_ID')]
  [Id('FID', TIdGenerator.IdentityOrSequence)]
  TChiamataDett = class(TChiamata)
  private


    [Column('Descrizione', [TColumnProp.Required], 1000)]
    FDescrizione: string;

    [Association([TAssociationProp.Required], CascadeTypeAll - [
       TCascadeType.Remove])]
    [ForeignKey('FARMACIA_CHIAMATADETT')]
    [JoinColumn('IDFarmacia', [TColumnProp.Required], 'ID')]
    FFarmacia: TFarmacia;
  public

    property Descrizione: string read FDescrizione write FDescrizione;
    property Farmacia: TFarmacia read FFarmacia write FFarmacia;
  end;

[Entity]
  [Model('Chiamate')]
  [Table('Farmacia')]
  [Sequence('SEQ_FARMACIA_ID')]
  [Id('FID', TIdGenerator.IdentityOrSequence)]
  TFarmacia = class
  private
    [Column('ID', [TColumnProp.Required])]
    FID: integer;
    
    [Column('Nome', [TColumnProp.Required], 50)]
    FNome: string;
    
    [Column('Citta', [TColumnProp.Required], 50)]
    FCitta: string;

    [Column('Telefono', [TColumnProp.Required], 50)]
    FTelefono: string;

    [Column('GUID', [TColumnProp.Required], 40)]
    FGUID: string;
  public
    property ID: integer read FID write FID;
    property Nome: string read FNome write FNome;
    property Citta: string read FCitta write FCitta;
    property Telefono: string read FTelefono write FTelefono;
    property GUID: string read FGUID write FGUID;
  end;

  [Entity]
  [Model('Chiamate')]
  [Table('TipoChiamata')]
  [Id('FID', TIdGenerator.None)]
  TTipoChiamata = class
  private
    [Column('ID', [TColumnProp.Required])]
    FID: integer;

    [Column('Descrizione', [TColumnProp.Required], 50)]
    FDescrizione: string;
  public
    property ID: integer read FID write FID;
    property Descrizione: string read FDescrizione write FDescrizione;
  end;



Your mapping is wrong, if you want to use inheritance you must explicitly use [Inheritance] attribute and define the table only in the root of hierarchy, specifying the discriminator column and values.

I tried in this way because I use TChiamata to obtain the list containing only few information, indeed when I want to obtain all the details I use TChiamataDett. In another test I used the [inheritance] attribute because I had the classic classes TPerson and the inherited TEmployee and TCustomer, but in this case I don't have any discriminator column.


P.S.: I tried also adding a "discriminator" column in table ChiamataDett, but when I start the server, it raise an exception and the message is "Cannot create entity type from class TChiamataDett. Class is not mapped." I modified the model in this way:

[Entity]
  [Model('Chiamate')]
  [Table('ChiamataDett')]
  [Inheritance(TInheritanceStrategy.SingleTable)]
  [DiscriminatorColumn('DISCR', TDiscriminatorType.dtInteger)]
  [Sequence('SEQ_CHIAMATA_ID')]
  [Id('FID', TIdGenerator.IdentityOrSequence)]
  TChiamata = class
    ....
  end;
  
  [DiscriminatorValue(0)]
  TChiamataDett = class(TChiamata)
    ....
  end;
 

I fixed my model, I forgot the [Entity] and [Model] attributes on TChiamataDett, but the result is the same, I always get an access violation on the OldState as before.

Can you please post your full mapping you are using now, and the exact code that is causing the error (what operations are you trying to do, in what context).

Here's the full model:

  TChiamata = class;
  TChiamataDett = class;
  TFarmacia = class;
  TTipoChiamata = class;
  
  [Entity]
  [Model('Chiamate')]
  [Table('ChiamataDett')]
  [Sequence('SEQ_CHIAMATA_ID')]
  [Inheritance(TInheritanceStrategy.SingleTable)]
  [DiscriminatorColumn('DISCR', TDiscriminatorType.dtInteger)]
  [Id('FID', TIdGenerator.IdentityOrSequence)]
  TChiamata = class
  private
    [Column('ID', [TColumnProp.Required])]
    FID: integer;

    [Association([TAssociationProp.Required], CascadeTypeAll - [TCascadeType.Remove])]
    [JoinColumn('IDTipologia', [TColumnProp.Required], 'ID')]
    FTipoChiamata: TTipoChiamata;

    [Column('GUID', [TColumnProp.Required])]
    FGUID: string;



    [Column('TS', [TColumnProp.Required])]
    FTS: TDateTime;

    [Column('DataOra', [TColumnProp.Required])]
    FDataOra: TDateTime;
  public
    procedure destroyTipoChiamata;
    property ID: integer read FID write FID;
    property TipoChiamata: TTipoChiamata read FTipoChiamata write FTipoChiamata;
    property GUID: string read FGUID write FGUID;
    property TS: TDateTime read FTS write FTS;
    property DataOra: TDateTime read FDataOra write FDataOra;
  end;

  [Entity]
  [Model('Chiamate')]
//  [Table('ChiamataDett')]
//  [Sequence('SEQ_CHIAMATA_ID')]
//  [Id('FID', TIdGenerator.IdentityOrSequence)]
  [DiscriminatorValue(0)]
  TChiamataDett = class(TChiamata)
  private

    
    [Column('Descrizione', [TColumnProp.Required], 1000)]
    FDescrizione: string;
    
    [Association([TAssociationProp.Required], CascadeTypeAll - [TCascadeType.Remove])]
    [JoinColumn('IDFarmacia', [TColumnProp.Required], 'ID')]
    FFarmacia: TFarmacia;
  public
    procedure destroyFarmacia;
    property Descrizione: string read FDescrizione write FDescrizione;
    property Farmacia: TFarmacia read FFarmacia write FFarmacia;
  end;
  
  [Entity]
  [Table('Farmacia')]
  [Model('Chiamate')]
  [Sequence('SEQ_FARMACIA_ID')]
  [Id('FID', TIdGenerator.IdentityOrSequence)]
  TFarmacia = class
  private
    [Column('ID', [TColumnProp.Required])]
    FID: integer;
    
    [Column('Nome', [TColumnProp.Required], 50)]
    FNome: string;
    
    [Column('Citta', [TColumnProp.Required], 50)]
    FCitta: string;

    [Column('Telefono', [TColumnProp.Required], 15)]
    FTelefono: string;

    [Column('GUID', [TColumnProp.Required], 40)]
    FGUID: string;
  public
    property ID: integer read FID write FID;
    property Nome: string read FNome write FNome;
    property Citta: string read FCitta write FCitta;
    property Telefono: string read FTelefono write FTelefono;
    property GUID: string read FGUID write FGUID;
  end;
  
  [Entity]
  [Model('Chiamate')]
  [Table('TipoChiamata')]
  [Id('FID', TIdGenerator.None)]
  TTipoChiamata = class
  private
    [Column('ID', [TColumnProp.Required])]
    FID: integer;

    [Column('Descrizione', [TColumnProp.Required], 50)]
    FDescrizione: string;
  public
    property ID: integer read FID write FID;
    property Descrizione: string read FDescrizione write FDescrizione;
  end;
  
This is the class that handles any request from server to client and an enumeration to set the RestMethod:

TRestMethod = (rmPOST,rmGET,rmPUT,rmDELETE);

TRestClient = class
  private
    FRestMethod: TRestMethod;
    FXDataClient: TXDataClient;
    procedure setReturnedInstancesOwnership(const Value: boolean);
  public
    constructor create(Method: TRestMethod);
    destructor Destroy; override;
    function getObject<T: class, constructor>(AID: integer): T;
    function getList<T:Class,constructor>(prop: string): TList<T>;
    procedure execute(AObject: TObject);
    function generateGUID: string;
    function getChiamataDettByGUID(AGUID: string): TChiamataDett;
    function getFarmaciaByGUID(AGUID: string): TFarmacia;
    function getFarmaciaByNome(AFiltro: string): TList<TFarmacia>;
    function getChiamataByData(ADataI, ADataF: TDateTime): TList<TChiamata>;
    function getChiamataByDescrizione(AString: string): TList<TChiamata>;
    function getChiamataByFarmacia(AID: integer): TList<TChiamata>;
    function getChiamataByTipologia(AParams: string): TList<TChiamata>;
    function verificaChiamataDett(AID: integer; ATS: TDateTime): integer;
    property RestMethod: TRestMethod read FRestMethod write FRestMethod;
    property XDataClient: TXDataClient read FXDataClient write FXDataClient;
    property ReturnedInstancesOwnership: boolean write
      setReturnedInstancesOwnership;
  end;

and this is the procedure on client side that performs a request to the server:

procedure TRestClient.execute(AObject: TObject);
begin
  case FRestMethod of
    rmPOST:
      FXDataClient.Post(AObject);
    rmPUT:
      FXDataClient.Put(AObject); //causes problems
    rmDELETE:
      FXDataClient.Delete(AObject);
  end;
end;

in particular, on server side, in Aurelius.Engine.ObjectManager, in procedure TObjectManager.PerformUpdate(Entity: TObject; ChangedColumns: TList<string>) on this point 

    Entry := FObjects.GetEntry(Entity);

    // Prepare old state for events
    if Events.OnUpdated.HasListeners then
    begin
      PreviousOldState := TObjectState.Create;
      for Pair in Entry.OldState.Values do //ERROR ACCESS VIOLATION
        PreviousOldState.Values.Add(Pair.Key, Pair.Value);
    end;

I get error every time I try to perform an update on TChiamataDett and when the access violation is thrown it is working on TFarmacia, trying to update it, but when it arrives at

   for Pair in Entry.OldState.Values do

Entry.OldState is set as nil and I get error

Can you please try to isolate and first test the update in a local database (without using XData)? Can you simply update your TChiamataDett in the database with a simple code?

What is the JSON sent from the client?

I tried it without XData and it works fine. How can I find the JSON sent from the client?

We have been able to reproduce and confirmed this is a problem. We have released the 2.7.1 version that fixes this problem.