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.
This topic was automatically closed 60 minutes after the last reply. New replies are no longer allowed.