XData/Aurelius and LEFT JOIN (once again)

It's a work in progress, but I don't know if I can make it today.

Now I have testfiles.
After a lot of testing, I get a resultlist.

I am testing with postman.

In the test programm, I have a "invalid pointer" error, but I get a list.
But the list (several entries) has empty content

I my real program, the list is correct, but I have memory problems.

see unit test.func.impl.pas

The difference is:
real: TObjectManager is included in a local interface variable
test: I create a TObjectManager and destroy it

In the future I will probably define all queries (which should deliver similar results) in a database view and create a separate memory entity in the XData server.
I think, this is a better way for me.

Thak you for all help

test.app.pas (5.0 KB) test.database.dfm (869 Bytes) test.database.pas (3.1 KB) test.entities.pas (16.9 KB) test.func.impl.pas (2.0 KB) test.func.pas (483 Bytes) test.logging.pas (1.6 KB) test.server.dfm (830 Bytes) test.server.pas (2.2 KB) test.types.pas (3.0 KB) XDataServer.dpr (620 Bytes) XDataServer.dproj (62.8 KB)

Today I have tested a DB-View with an own Entity declaration.
I think that is much easier than any other variants. I can implement this with real DB-Views or with a query build in my own server.

I must set the ObjectManager.OwnsObjects := False;
Than it works without error and without memory leaks.

But the Entity must have

  [Entity]
  [Model('modelname')]
  [Table('viewname')]
  [Id('ID_of_tab1, TIdGenerator.None)]
  [Id('ID_of_tab2', TIdGenerator.None)]

Maybe you can tell me what I have to change in the entity declaration when I send a simple SQL statement instead of a view

The problem in your test.func.impl.TTestFunc.GetServicelist method is that you are retrieving entities using a TObjectManager to be returned by the function, but then you are destroying that manager - thus, the entities will be destroyed.

I fail to see why are you creating a TObjectManager instance yourself instead of using the one available from the context. That's exactly one reasons to use the context manager, besides the fact it uses a connection from the pool: the way you are creating your manager is not thread-safe because you are using the global dmDatabase.DBConnection which is not safe.

So, use the manager from the context and all will be fine.

If you absolute want or need to create your own manager, don't destroy it, but let XData know that it should destroy it - so your entities won't get destroyed until they are serialize to JSON. Use the following code to tell XData to destroy the manager only after everything is finished:

  ObjManager := TObjectManager.Create(Conn, Model);
  TXDataOperationContext.Current.AddManager(ObjManager);

This is explained in details here, in section "Additional Managers":

https://download.tmssoftware.com/business/xdata/doc/web/txdataoperationcontext.html

Oh, this is easy to explain.
The first approach came from the book "Introducing Delphi ORM". A session object was used there.
The second is, I want to use transactions, events, and more in one place.
And later I must extend the server to replicate all things to another server. I don't know, if TMS Echo does that all.

Our system will later have 10-20 system services (TMS XData) plus many individual project services.
A Kind of a microservices system.

I take any help to make it better.

unit sng.dbSession.Impl;

interface

uses
  System.Classes, System.Generics.Collections,
  XData.Server.Module,
  Aurelius.Engine.DatabaseManager,
  Aurelius.Engine.ObjectManager,
  Aurelius.Drivers.Interfaces,
  Aurelius.Mapping.Explorer,
  Aurelius.Events.Manager,
  sng.dbSession;

type
  TDBSession = class(TInterfacedObject, IDBSession)
  private
    FConnection       : IDBConnection;
    FModelName        : String;
    FUser             : TGuid;
    FDbManager        : TDatabaseManager;
    FObjManager       : TObjectManager;
    FTransaction      : TList<IDBTransaction>;
  protected
    procedure OnInserting( Args: TInsertingArgs);
    procedure OnUpdating( Args: TUpdatingArgs);
  public
    constructor Create( const AConnection: IDBConnection; const AModelName: String; const AUser: TGuid);
    destructor Destroy; override;

    function DBManager: TDatabaseManager;
    function ObjManager: TObjectManager;

    procedure StartTransaction;
    procedure Commit;
    procedure Rollback;

    function InTransaction: Boolean;
    procedure ClearTransaction;

    //property ModelName: String read FModelName;
  end;

//var
//  xxLongTermSession: TDBSession;

implementation

uses
  System.SysUtils,
  sng.consts,
  sng.dbEntities;

{ TDBSession }

constructor TDBSession.Create(const AConnection: IDBConnection; const AModelName: String; const AUser: TGuid);
begin
  Assert( AConnection <> nil, rsConnectionNil);
  inherited Create;
  FModelName   := AModelName;
  FUser        := AUser;
  FTransaction := TList<IDBTransaction>.Create;
  FConnection  := AConnection;
  TMappingExplorer.Get(FModelName).Events.OnInserting.Subscribe( OnInserting);
  TMappingExplorer.Get(FModelName).Events.OnUpdating.Subscribe( OnUpdating);
end;

destructor TDBSession.destroy;
begin
  TMappingExplorer.Get(FModelName).Events.OnInserting.Unsubscribe( OnInserting);
  TMappingExplorer.Get(FModelName).Events.OnUpdating.Unsubscribe( OnUpdating);
  FTransaction.Free;
  FDbManager.Free;
  FObjManager.Free;
  inherited;
end;

procedure TDBSession.OnInserting( Args: TInsertingArgs);
begin
  if Args.Entity is TsngEntity then
    with Args.Entity as TsngEntity do begin
      sngCreate     := now;
      sngCreateUser := FUser;
      sngChange     := now;
      sngChangeUser := FUser;
    end;
end;

procedure TDBSession.OnUpdating( Args: TUpdatingArgs);
begin
  if Args.Entity is TsngEntity then begin
    with Args.Entity as TsngEntity do begin
      sngChange     := now;
      sngChangeUser := FUser;
    end;
    Args.RecalculateState := True;
  end;
end;

function TDBSession.DBManager: TDatabaseManager;
begin
  if not Assigned( FDbManager) then
    FDbManager := TDatabaseManager.Create( FConnection);
  result := FDbManager;
end;

function TDBSession.ObjManager: TObjectManager;
begin
  if not Assigned( FObjManager) then
    FObjManager := TObjectManager.Create( FConnection, TMappingExplorer.Get( FModelName));
  result := FObjManager;
//  result := TXDataOperationContext.Current.GetManager;
end;

procedure TDBSession.StartTransaction;
begin
  FTransaction.Add( FObjManager.Connection.BeginTransaction);
end;

procedure TDBSession.Commit;
begin
  if FTransaction.Count > 0 then begin
    FTransaction.Last.Commit;
    FTransaction.Delete( FTransaction.Count-1);
  end;
end;

procedure TDBSession.Rollback;
begin
  if FTransaction.Count > 0 then begin
    FTransaction.Last.Rollback;
    FTransaction.Delete( FTransaction.Count-1);
  end;
end;

function TDBSession.InTransaction: Boolean;
begin
  Result := FTransaction.Count > 0;
end;

procedure TDBSession.ClearTransaction;
begin
  for var i:Integer := FTransaction.Count-1 downto 0 do
    FTransaction.Delete(i);
end;

end.

I don't think your TDBSession is needed. That transaction control is performed by Aurelius already - starting and committing a transaction when there is already another active transaction will do nothing.

Second, the TObjectManager you return doesn't add any important business logic. So you can simply use the one from context.

Third, the events should be set globally, once, for the model. Events are in the context of the model, not the connection, so you are adding and removing event handlers all the time without need, and it's actually not thread-safe, and it's also executing multiple event handlers at once if you have multiple TDBSession objects being used in different threads.

Ok,I will to try to change that accordingly.
I will post here, when new problems arise.

Thank you, Wagner, very much.

1 Like

This topic was automatically closed 60 minutes after the last reply. New replies are no longer allowed.