#51 Re: mORMot 1 » TObjectList<T> as a service method parameter » 2015-07-28 12:43:23

In future I would like to extend the TMeasurement class by a new property Data: TSQLRawBlob.
The client could define then about 250 000 TMeasurement objects and send it through the service to the server. The amount of the data in one TSQLRawBlob property could be up to 1 MB. Could you please advise me how I can handle it with the mORMot framework in the best and proper way?

Thank you for your time and help in advance! :-)

#52 Re: mORMot 1 » TObjectList<T> as a service method parameter » 2015-07-28 12:34:43

ab wrote:

TObjectList<TMeasurement> is not handled yet.
It is very difficult to retrieve the actual internal type of a generic type, even with enhanced RTTI.

Use T*ObjArray instead.
See http://synopse.info/files/html/Synopse% … #TITLE_555

Just want to know if my code is fine and doesn't have any memory leaks. I've changed my code as follows.

My custom data types:

  TMeasurements = array of TMeasurement;

  TMeasurementJob = class(TSynPersistent)
  private
    fMeasurements: TMeasurements;
  protected
    procedure SetMeasurements(const aValue: TMeasurements);
  published
    property Measurements: TMeasurements read fMeasurements write SetMeasurements;
  end;

initialization
  TJSONSerializer.RegisterClassForJSON([TMeasurementJob, TMeasurement]);
  TJSONSerializer.RegisterObjArrayForJSON([TypeInfo(TMeasurements), TMeasurement]);

Usage:

// client side
procedure SendDataToServer;
var
  Measurements: TMeasurements;
  aMeasurement: TMeasurement;
begin
  aJob := TMeasurementJob.Create;
  try
    for I := 0 to 10 do
    begin
      aMeasurement := TMeasurement.Create;
      ...
      ObjArrayAdd(Measurements, aMeasurement);
    end;
    
    aJob.Measurements := Measurements;
    
    // send data to the server
    fJobService.SaveMeasurementJob(aJob);

  finally
    ObjArrayClear(Measurements);
    aJob.Free;
  end;  
end;

Is it possible to avoid the additional declaration of the *Measurements: TMeasurements;* and to add the aMeasurement objects directly to the Measurement property of the aJob object?

#53 mORMot 1 » How transfer a TSQLRawBlob field as JSON » 2015-07-28 12:15:58

cypriotcalm
Replies: 4

I tried to find this option in the documentation, but I failed :-(

I have a class:

  TMyJSONData = class(TSynPersistent)
  ...
  published
    property Data: TSQLRawBlob read fData write SetData;
  end;

I pass some data to the service, on the client side everything is okay, but on the server side I don't get the binary data transmitted.

  //CLIENT SIDE        
  aJPEG.LoadFromFile('C:\Users\Public\Pictures\Sample Pictures\Desert.jpg');
  aMyJSONData.Data := StreamToRawByteString(aJPEG); 

  ....
  DataImportService.Save(aMyJSONData);


  // SERVER SIDE
  aDevice.Data -> the property is empty

How do I enable the transfering of the TSQLRawBlob fields?!

The only one thing what I found in the documentation was:
"- by default, TSQLRawBlob properties are serialized as null, unless woSQLRawBlobAsBase64 is defined"

But it was not explained how and where I can do this!

Thx for your help!

#54 mORMot 1 » TObjectList<T> as a service method parameter » 2015-07-27 07:15:12

cypriotcalm
Replies: 6

Hello,

I have the following code:

  TMeasurementJob = class(TSynPersistent)
  private
    fMeasurements: TObjectList<TMeasurement>;
  protected
    procedure SetMeasurements(const aValue: TObjectList<TMeasurement>);
  public
    constructor Create;
    destructor Destroy; override;
  published
    property Measurements: TObjectList<TMeasurement> read fMeasurements write SetMeasurements;
  end;

constructor TMeasurementJob.Create;
begin
  fMeasurements := TObjectList<TMeasurement>.Create(True);
end;  

destructor TMeasurementJob.Destroy;
begin
  fMeasurements.Free;
  inherited;
end;

initialization
  TJSONSerializer.RegisterClassForJSON([TMeasurementJob, TMeasurement]);

Now, I create a service:

  IJobService = interface(IInvokable)
  ['{4CAE54FA-2371-40D9-9FD4-DEC80A34803D}']
    function SaveMeasurementJob(const aJob: TMeasurementJob): Boolean;
  end;

There is the following code in a delphi client:

    aJob := TMeasurementJob.Create;

    for I := 0 to 2 do
      aJob.Measurements.Add(TMeasurement.Create);

    aJob.Free;

    ...

    fJobService.SaveMeasurementJob(aJob);

And the problem is that on the server side the Job-Object doesn't contain any measurements, i.e. the list aJob.Measurements is empty.

My server and client are ClientDriven.

What am I doing wrong?

Thx for the answer!

#56 mORMot 1 » InterfaceToJSON/JSONToInterface possible? » 2015-07-17 07:44:46

cypriotcalm
Replies: 2

why is it not possible to serialize interfaces?

#57 Re: mORMot 1 » free objects on the server side » 2015-07-15 09:28:48

ab wrote:

JSON content should be cached somewhere.

Please try to disable SQlite3 cache.

I've tried it as follows, but the problem remains...

...
  ServerDB := TSQLRestServerDB.Create(Model, ChangeFileExt(ExeVersion.ProgramFileName, '.db3'), False);
  ServerDB.DB.UseCache := False;
  ServerDB.CreateMissingTables;
...

#58 Re: mORMot 1 » free objects on the server side » 2015-07-15 09:06:31

ab wrote:

Yes, there was an issue here.

Fixed by http://synopse.info/fossil/info/f83aed8e10

Thanks for the feedback.

Thx for the fixing!

But the problem with the growing size of the server-"exe" remains... after the http-request, e.g. http://serverhost:7979/root/messartservice/SelectByManufacturer?aManufacturer=SMITH, I get about 294912 records a database. It seems that the "exe" does free not everything.

#59 Re: mORMot 1 » Taming the Mormot » 2015-07-15 07:38:59

willo wrote:

Thanks guys!

Many thanks to you. I can see that my approaches to use the framework are right, or go in the same direction! :-D *feeling happy*

#60 Re: mORMot 1 » free objects on the server side » 2015-07-14 15:20:48

Junior/RO wrote:

I can't get it.

function TSQLRest.RetrieveListObjArray() is Boolean, but you are using Result as a [absolute] TObjectDynArray in ObjArrayClear()

I don't want to use the boolean result of the function RetrieveListObjArray, the result of SelectByManufacturer() should be filled by RetrieveListObjArray(Result,...)

#61 Re: mORMot 1 » free objects on the server side » 2015-07-14 12:49:51

ab wrote:

How did you write TMessartService.SelectByManufacturer ?

TMessartObjArray = array of TMessart;

function TMessartService.SelectByManufacturer(const aManufacturer: TName): TMessartObjArray;
begin
  ServiceContext.Factory.Rest.RetrieveListObjArray(Result, TMessart, 'ManufacturerID = ?', [aManufacturer]);
end;

#62 Re: mORMot 1 » free objects on the server side » 2015-07-14 07:56:36

"exe" memory is growing...
I call the service-function (http-request) which retrieves a big amount of data from the external database (MySQL/FireDAC). And on the server in the task manager the "exe" consumes more memory, in my case it has the size of 500MB after the first request. But in browser I get the data as JSON.


I have also rewritten the code as you have said, but now I get an access violation in TSQLRest.RetrieveListArray:

function TSQLRest.RetrieveListObjArray(var ObjArray; Table: TSQLRecordClass;
  const FormatSQLWhere: RawUTF8; const BoundsSQLWhere: array of const;
  const aCustomFieldsCSV: RawUTF8): boolean;
var T: TSQLTable;
begin
  result := false;
  if (self=nil) or (Table=nil) then
    exit;
  T := MultiFieldValues(Table,aCustomFieldsCSV,FormatSQLWhere,BoundsSQLWhere);
  if T<>nil then
  try
    result := T.ToObjArray(result,Table); <------- HERE access violation
  finally
    T.Free;
  end;
end;


function TSQLTable.ToObjArray(var ObjArray; RecordType: TSQLRecordClass=nil): boolean;
var R: TSQLRecord;
    Row: PPUtf8Char;
    i: integer;
    arr: array of TSQLRecord absolute ObjArray;
begin
  result := false;
  ObjArrayClear(ObjArray); <------- HERE access violation
  if self=nil then
    exit;
  if RecordType=nil then begin
    RecordType := QueryRecordType;
    if RecordType=nil then
      exit;
  end;
  result := true;
  if fRowCount=0 then
    exit;
  R := RecordType.Create;
  try
    R.FillPrepare(self);
    SetLength(arr,fRowCount);        // faster than manual Add()
    Row := @fResults[FieldCount];   // Row^ points to first row of data
    for i := 0 to fRowCount-1 do begin
      arr[i] := RecordType.Create;
      R.fFill.Fill(pointer(Row),arr[i]);
      Inc(Row,FieldCount); // next data row
    end;
  finally
    R.Free;
  end;
end;


procedure ObjArrayClear(var aObjArray);
var i: integer;
    a: TObjectDynArray absolute aObjArray;
begin
  if a<>nil then begin
    for i := 0 to length(a)-1 do <------- HERE access violation
      a[i].Free;
    a := nil;
  end;
end;

#63 Re: mORMot 1 » free objects on the server side » 2015-07-13 11:51:54

ab wrote:

There is no reason why the direct HTTP request may consume server memory.
The TObjectList is serialized in to a JSON response, this this JSON string is passed to the client, and released as soon as it has been sent.

I don't understand then why the server-exe is getting bigger....

ab wrote:

Your SelectByManufacturer() method sounds fine.
I guess there is a leak somewhere else in your code.

I have these methods :-)


I initialize like follows:

  // SERVER
  TMessartService = class(TInterfacedObject, IMessartService)
  private
    function fRest: TSQLRest;
  published
    procedure SelectByManufacturer(const aManufacturer: TName; out aMessarts: TObjectList);
  end;  

  function TMessartService.fRest: TSQLRest;
  begin
    Result := ServiceContext.Factory.Rest;
  end;  

  procedure TMessartService.SelectByManufacturer(const aManufacturer: TName; out aMessarts: TObjectList);
  begin
    aMessarts := fRest.RetrieveList(TMessart, 'ManufacturerID = ?', [aManufacturer]);
  end;

  ...
  procedure TFrmMainServer.InitHTTPService;
  begin
    InitializeModel(Model, DBConnectionProperties);

    ServerDB := TSQLRestServerDB.Create(Model, ChangeFileExt(ExeVersion.ProgramFileName, '.db3'), False);
    ServerDB.CreateMissingTables;

    ServerDB.AcquireExecutionMode[execORMWrite] := amBackgroundThread;

    // Messart service
    ServerDB.ServiceDefine(TMessartService, [IMessartService], sicClientDriven);

    ServerHTTP := TSQLHttpServer.Create('7979', [ServerDB], '+', useHttpApiRegisteringURI);
    ServerHTTP.AccessControlAllowOrigin := '*'; // allow cross-site AJAX queries
  end;


  // CLIENT
  FModel := TSQLModel.Create([]);

  FClient := TSQLHttpClient.Create('serverhost','7979', FModel);

  FClient.ServiceDefine([IMessartService], sicClientDriven);

  if not FClient.Services['MessartService'].Get(FMessartService) then
    raise Exception.Create('Can''t get the service'); 


 procedure TFrmMainClient.Button1Click(Sender: TObject);
 var
   aMessartList: TObjectList;
 begin
   aMessartList := TObjectList.Create;
   try
     FMessartService.SelectByManufacturer('smith', aMessartList);
     ShowMessage('Done! '+IntToStr(aMessartList));
   finally
     aMessartList.Free;
   end;
 end;

This is my whole application! There is no more code!

Now, I have another problem. If I call on the client FMessartService.SelectByManufacturer('smith', aMessartList) I get an error:

   EInterfaceFactoryException: TInterfacedObjectFakeClient failed: returned object

If I change the code on the server like follows:

  procedure TMessartService.SelectByManufacturer(const aManufacturer: TName; out aMessarts: TObjectList);
  var
    LocalMessartList: TObjectList;
  begin
    LocalMessartList := fRest.RetrieveList(TMessart, 'ManufacturerID = ?', [aManufacturer]);
  end;

Everything is ok, the data is retrieved from the database. But I can't pass it back to the client :-(

What am I doing wrong?! It's really frustrating to have such bugs and the code is very simple! :-(

Thank you very much for your help!!!

#64 Re: mORMot 1 » free objects on the server side » 2015-07-13 06:56:21

Thx for the answer, but I want to call my data per http-request, NOT in delphi. I want to retrieve the data for example in browser with uri

http://server:1234/root/messartservice/SelectByManufacturer?aManufacturer=qwerty

and I do this then I get the situation like stated in my first post. Please read it again!

#65 Re: mORMot 1 » free objects on the server side » 2015-07-10 08:45:16

zed wrote:

Simple call aMessarts.Free after use?

If it would be so simple ;-) after the call .free I get an "EInvalidPointer"-error :-(

#66 mORMot 1 » free objects on the server side » 2015-07-10 07:58:39

cypriotcalm
Replies: 36

Hello,

I have a service on the server:

  IMessartService = interface(IInvokable)
    procedure SelectByManufacturer(const aManufacturer: TName; out aMessarts: TObjectList);
  end;
  
  ...
  ...
  
  procedure SelectByManufacturer(const aManufacturer: TName; out aMessarts: TObjectList);
  begin
    aMessarts := fRest.RetrieveList(TMessart, 'ManufacturerID = ?', [aManufacturer]);
  end;  

Now, I want to retrieve the data per http-request: (data amount is about 300 000 rows from external database)

http://server:1234/root/messartservice/SelectByManufacturer?aManufacturer=qwerty

If I do so, the size of the mORMot-Server-Exe is getting bigger and has about 500 MB. And each request increases the EXE-size.

How should I do the memory management in this case?

My service is defined as ClientDriven.

I coudn't find any information about that in the documentation and in froum :-(

#67 Re: mORMot 1 » JSON result on REST request » 2015-06-26 05:18:57

I rewrited my code as follows:

  // THESE ARE MY DTO TYPES
  TMessart = class(TSynPersistent)
  private
    FName: TName;
  published
    property Name: TName read FName write FName;
  end;

  TMessarts = array of TMessart;

  initialization
    TJSONSerializer.RegisterObjArrayForJSON(TypeInfo(TMessarts), TMessart);
  // THESE ARE MY DAO TYPES
  type
    TDAOMessart = class(TSQLRecord)
    private
      FName: TName;
    published
      property Name: TName read FName write FName;
    end;

    TDAOMessarts = array of TObjectDynArray;

    initialization
      TJSONSerializer.RegisterObjArrayForJSON(TypeInfo(TDAOMessarts), TDAOMessart);
  // SERVICE METHOD
  function TMessartService.SelectAll(out aMessarts: TMessarts): TCQRSResult;
  
  // MESSART COMMAND FACTORY
  constructor TMessartCommandFactory.Create(ARest: TSQLRest; AOwner: TDDDRepositoryRestManager = nil);
  begin
    inherited Create(IMessartService, TMessartService, TDAOMessart, aRest, TDAOMessart, aOwner);
  end;

My messart service method looks like:

function TMessartService.SelectAll(out aMessarts: TMessarts): TCQRSResult;
var
  I: Integer;
  DAOMessarts: TDAOMessarts;
  DTOMessart: TMessart;
begin
  // 1. Select data from the database
  Result := ORMSelectAll('',[]);
  if (Result = cqrsSuccess) then
  begin
    // 2. Get the database data as aggregates, in my case an aggregate is a TSQLRecord class
    Result := ORMGetAllAggregates(PhoDAOMessarts);
    if (Result = cqrsSuccess) then
    begin
      // 3. Initialize the dyn array with the DTO objects and iterate all aggregates
      SetLength(aMessarts, Length(DAOMessarts));
      for I := Low(DAOMessarts) to High(DAOMessarts) do
      begin
        // 4. Create an DTO object for the client output 
        aMessarts[i] := TMessart.Create;
        // 5. Do the "object property conversion", i.e. DTOMessart.Name := DAOMessart.Name
        fDAOConverter.ConvertToDTO(TSQLRecord(DAOMessarts[i]), aMessarts[i]);
      end;
    end;
  end;
end;

On this way my JSON result looks like as expected.

  {"result": [[{"Name": "Messart 1"},{"Name": "Messart 2"}], 0],"id": 2}

Q1: The zero-value doesn't change. What is that value? I have also changed sicSingle, sicShared, sicClientDriven, but the zero-value is still there.
Q2: "id": 2 is changing by each request.

My questions to the code of my service method:
Q3: who and where should be freed the created object in the service method in the step 4? How do I handle properly the memory in that case?

Generally, I am trying to follow the DDD approach like you have described it in the documentation. I'm new to that kind of things.
Q4: What do you think, does my code above fully follow the DDD approach? Or do I mix some things and doing it in a wrong way?

I try to separate the data layer classes (DAOs) and the business domain classes (DTOs), so I do the "object conversion" (see the service method step 5).
Q5: Is it a right and efficient way to do the things like that? Does it make sense to do it? How would you do that?

p.s. sorry for many, and maybe newbie questions, but I'm a new to that kind of stuff and want to learn to do it on the proper way! Thank you for your answers and investing time in that in advance! :-)

#68 Re: mORMot 1 » JSON result on REST request » 2015-06-26 04:30:06

ab wrote:

I suspect you are mixing things.

Inherit TMessart from TSynPersistent.
Your TMessart has no RTTI at all.
So I guess this is another class.

I have added the inheritance, but nothing has changed. I have the same output with IDs which are zeros, but I don't have any ID field in the DTO class TMessart defined!? Why do the IDs appear in the result JSON? What things am I mixing?!

#69 mORMot 1 » JSON result on REST request » 2015-06-25 10:17:30

cypriotcalm
Replies: 4

I have an DTO object and a service:

type
  TMessart = class(TObject)
  private
    FName: TName;
  published
    property Name: TName read FName write FName;
  end;

  TMessarts = array of TMessart;

  initialization
    TJSONSerializer.RegisterObjArrayForJSON(TypeInfo(TMessarts), TMessart);

  ...

  // SERVICE
  function TMessartService.SelectAll(out aMessarts: TMessarts): TCQRSResult;

When I execute an REST request I get some like that:

  http://localhost:7979/root/messartservice/SelectAll
  
  {"result": [[{"ID": 0,"Name": "Messart 1"},{"ID": 0,"Name": "Messart 2"}], 0],"id": 2}

Q1: What are these Zero-IDs? I don't have them in my DTO class.
Q2: Where comes the last zero value in the array from? And what does it mean?
Q3: At each request the "id" is changing. Why and on what purpose?

p.s. sorry for the maybe stupid questions, but I want to fully understand how the framework works! :-)

#70 Re: mORMot 1 » database connections handling » 2015-06-25 07:24:17

Great! It works! Thank you very much for your fast answer!

#71 mORMot 1 » database connections handling » 2015-06-25 06:36:41

cypriotcalm
Replies: 2

Hi Ab,

I have a server which I initialize as follows below. In my model I have a SQLRecord class which is TMessart. So, I start my server and do some REST requests from a client.

Q1: My REST requests works fine and I get data, but each time I send a http request to the server a new database connection is opened which is not closed after the request. Do you any idea how I can handle this?

Q2: In the code below if when ServerHTTP.Free is called an access violation occurs. If I debug into the method, the error is raised in THttpApiServer.Destroy the code check if fServerSessionID<>0 then begin
Is it a bug, or am I doing something wrong?

  // INITIALIZE THE SERVER
  DatabaseConnectionProperties := TSQLDBConnectionProperties.Create;

  Model := CreateModel;
  VirtualTableExternalRegister(Model, TMessart, DatabaseConnectionProperties, 'testdb.messart');

  ServerDB := TSQLRestServerDB.Create(Model, ChangeFileExt(ExeVersion.ProgramFileName, '.db3'), False);
  ServerDB.CreateMissingTables;

  ServerHTTP := TSQLHttpServer.Create('7979', [ServerDB], '+', useHttpApiRegisteringURI);
  ServerHTTP.AccessControlAllowOrigin := '*'; // allow cross-site AJAX queries  
  // DO SOME REST requests
  http://localhost:7979/root/messart/1
  http://localhost:7979/root/messart/2
  http://localhost:7979/root/messart/3
  ...
  ... and so on
  // SHUTDOWN THE SERVER
  DatabaseConnectionProperties.Free;
  Model.Free;
  ServerDB.Free;
  ServerHTTP.Free;

#72 Re: mORMot 1 » DDD ORMAdd, Commit and how to retrieve the inserted ID » 2015-06-19 04:27:09

ab wrote:

1. How do you call the method? From which thread?

I don't call this method directly if you mean CheckConnection, I call only the commit (see the code above). And by calling the method the exception is raised in the method CheckConnection

ab wrote:

3. In pure DDD you should not need to worry about the IDs, but identify your aggregate via some real data from the model (logon, account number, social insurance number...). The ID is an implementation detail.

In most cases the real data is identified via some unique things like logon or account number, but sometimes the primary keys from the databases are used as a unique identifier. This is a case if there is no real identifier, but we need one and we have to create some artificial one. And I think it's ok to use a primary key. What do you think? What is your experience?

#73 mORMot 1 » DDD ORMAdd, Commit and how to retrieve the inserted ID » 2015-06-18 19:19:37

cypriotcalm
Replies: 2

In my service I would like to have a method to create a new measurement type (TPhoMessart is a measurement type) in the database and to return an ID of the new record.

The following code works awesome:

function TPhoMessartService.CreateNewOne(const AName: RawUTF8): TID;
var
  NewMessart: TPhoMessart;
begin
  NewMessart := TPhoMessart.Create;
  try
    NewMessart.Name := AName;
    Result := Factory.Rest.Add(NewMessart, True);
  finally
    NewMessart.Free;
  end;
end;

But the documentation says that I have to use the methods ORMAdd and Commit. So, I want to follow the instruction and tried it out as follows:

function TPhoMessartService.CreateNewOne(const AName: RawUTF8): TID;
var
  NewMessart: TPhoMessart;
begin
  NewMessart := TPhoMessart.Create;
  try
    NewMessart.Name := AName;
    // Result := Factory.Rest.Add(NewMessart, True);
    ORMAdd(NewMessart);
    Commit;
  finally
    NewMessart.Free;
  end;
end;

If the code above is executed I am getting an exception "TSQLDBFireDACConnection on MySQL/[databasename] should be connected" during the executing of Commit. The exception is raised in the following method:

procedure TSQLDBConnection.CheckConnection;
begin
  if self=nil then
    raise ESQLDBException.Create('TSQLDBConnection not created');
  if not Connected then
    raise ESQLDBException.CreateUTF8('% on %/% should be connected',
      [self,Properties.ServerName,Properties.DataBaseName]);
end;

Question 1: What am I doing wrong? Because if I access the database through the Factory.Rest.Add(...) a MySQL connection is there and everything is fine.

Question 2: How do I get the ID of the new record after Commit?

Question 3: Is it a right way how I follow the DDD approach in my code?

Thank you for your help in advance!

#74 Re: mORMot 1 » DDD ORMSelectAll » 2015-06-18 18:46:10

ab wrote:

Note that returning ALL aggregates may not be DDD-aware at all...
The point of the Aggregate is to have all data for a given bounded context.
Retrieving all aggregates may not make sense...

It's a good point! I have already refactored some classes ;-) But sometimes I have to retrieve all records from a table for an overview, e.g. in my case I want to let a user gets an overview about all measurement types (TPhoMessart is a measurement type) in the system, so I have to call something like TPhoMessart.SelectAll(out AMessarts: TMessarts);

A further question:
Is it a good idea to use as an aggregate class the same class which maps my messart-table in the database, i.e. TPhoMessart for both as an aggregate and as TSQLRecord class?

#75 Re: mORMot 1 » DDD ORMSelectAll » 2015-06-18 11:32:11

noobies wrote:

try this?

function TPhoMessartCommand.Select: TCQRSResult;
begin
  Result := ORMSelectAll('', []);
end

I have already tried it out before, but it didn't work

#76 mORMot 1 » DDD ORMSelectAll » 2015-06-18 09:07:44

cypriotcalm
Replies: 4

There is a method ORMSelectAll in the class TDDDRepositoryRestQuery.

But with this method I can only select records meeting some WHERE-condition. How can I select all records, e.g. like select * from <table>, from a table?

Now I select all records in the following way:

function TPhoMessartCommand.Select: TCQRSResult;
begin
  Result := ORMSelectAll('?=?', [1,1]);
end;

Could we have something like? Would this make sense?

function TPhoMessartCommand.Select: TCQRSResult;
begin
  Result := ORMSelectAll('*', []);
end;

// or

function TPhoMessartCommand.Select: TCQRSResult;
begin
  Result := ORMSelectAll();
end;

#77 Re: mORMot 1 » interface inheritance at RegisterInterfaces internal error L2111 » 2015-06-18 06:50:59

ab wrote:

There should still be something wrong in your code.
How is your TPhoMessartCommand class defined?

Arnaud, you are right! smile This time it's my mistake wink

I have defined this class in the same way your documentation requires it.
The point is that I have to inject the resolver before defining the service when initializing the server instance. Otherwise, the "factory" field is not set and I get an violation error.
But on the client side I have to do it vice versa, i.e. I have to define first the service and then to resolve the interface.

  // server side
  (...)
  ServerDB.ServiceContainer.InjectResolver([TPhoMessartCommandFactory.Create(ServerDB)], True);
  ServerDB.ServiceDefine(TPhoMessartCommand, [IPhoMessartCommand], sicClientDriven);
  (...)

  // client side
  (...)
  Model := TSQLModel.Create([]);
  Client := TSQLHttpClient.Create('localhost','7979', FModel);
  
  Client.ServiceDefine([IPhoMessartCommand], sicClientDriven);
  Client.Services.Resolve(IPhoMessartCommand, MessartCommand);
  (...)
ab wrote:

Using the debugger, you would be able to find out how it works, even on client/server.

I am using the debugger all the time! ;-)


But now it works and I continue to discover the amazing framework! A big thank you for your help and fast fixing the issues! smile

#78 Re: mORMot 1 » interface inheritance at RegisterInterfaces internal error L2111 » 2015-06-18 05:55:49

ab wrote:

This automatic injection is tested by TInfraRepoUserFactory.RegressionTests, as defined in dddInfraRepoUser.
Up to now, only direct call from the server side was made.
I've enhanced the tests to use a client/server connection (as you do).
There was indeed an issue, which has been fixed by http://synopse.info/fossil/info/ff269c01b1

I have just downloaded the nightly build and tried it out. I am still getting the same error. The "factory" field is nil when the framework is calling the function TDDDRepositoryRestQuery.ORMSelectOne.

By debugging I have noticed that the instance of PhoMessartCommand is resolved but the "factory" field is not set. See the code below:

function TDDDRepositoryRestQuery.ORMSelectOne(ORMWhereClauseFmt: PUTF8Char;
   const Bounds: array of const; ForcedBadRequest: boolean): TCQRSResult;
begin
  CqrsBeginMethod(qaSelect,result);
  if ForcedBadRequest then
    CqrsSetResult(cqrsBadRequest) else
    CqrsSetResultSuccessIf(Factory.Rest.Retrieve(ORMWhereClauseFmt,[],Bounds,
      fCurrentORMInstance),cqrsNotFound);
end;
ab wrote:

But IMHO your code has still a problem: you define your IPhoMessartCommand service as sicShared.
A CQRS service is NOT meant to be defined as sicShared.
A CQRS service should have the client life-time, so should be defined as sicClientDriven.

Thx for the advice! I have changed it.

#79 Re: mORMot 1 » interface inheritance at RegisterInterfaces internal error L2111 » 2015-06-17 08:54:01

ab wrote:

The client side has nothing to do with the factory.
The factory is an implementation detail of TDDDRepositoryRestQuery, which stay on the server side.

I have thought the same smile

Is the reference to the factory not done with the followiing code?

  ServerDB.ServiceContainer.InjectResolver([TPhoMessartCommandFactory.Create(ServerDB)], True);
ab wrote:

I'll look further and come back.

Ok, thx, I'll wait

#80 Re: mORMot 1 » interface inheritance at RegisterInterfaces internal error L2111 » 2015-06-17 07:35:22

ab wrote:

Please use the debugger on the server side to find out why the factory is not resolved in your case.

You have to explicitly resolve the Factory reference, before using it.
You did not show this in your code.

Also on the client side?
How and where can I do this? Code you give me a short code example of it?

#81 Re: mORMot 1 » interface inheritance at RegisterInterfaces internal error L2111 » 2015-06-17 07:34:22

ab wrote:

Please use the debugger on the server side to find out why the factory is not resolved in your case.

I have debugged this code:

  ServerDB.Services.Resolve(IPhoMessartCommand, MessartCommand);

Everything seems to be ok. And I am getting true if the code below is executed:

function TInterfaceResolverInjected.Resolve(aInterface: PTypeInfo; out Obj): boolean;
begin
  if self=nil then
    result := false else
    result := TryResolve(aInterface,Obj);
end;

function TInterfaceResolverInjected.Resolve(const aGUID: TGUID; out Obj): boolean;
var known: TInterfaceFactory;
begin
  if self=nil then
    result := false else begin
    known := TInterfaceFactory.Get(aGUID);
    if known<>nil then
      result := Resolve(known.fInterfaceTypeInfo,Obj) else
      result := false;
  end;
end;

But I don't understand where the instance of the class TDDDRepositoryRestQuery is created.
The debugger doesn't call this code:

constructor TDDDRepositoryRestQuery.Create(aFactory: TDDDRepositoryRestFactory);
begin
  fFactory := aFactory;
  fCurrentORMInstance := fFactory.Table.Create;
end;

Do you have any idea?

#82 Re: mORMot 1 » interface inheritance at RegisterInterfaces internal error L2111 » 2015-06-17 06:42:30

Hi Arnaud,

thank you for your detailed answer!

I have tried it out, but I am getting an error. It's an access violation in the class TDDDRepositoryRestQuery. The field value Factory is nil. See the code below.

function TDDDRepositoryRestQuery.ORMSelectOne(ORMWhereClauseFmt: PUTF8Char; const Bounds: array of const; ForcedBadRequest: boolean): TCQRSResult;
begin
  CqrsBeginMethod(qaSelect,result);
  if ForcedBadRequest then
    CqrsSetResult(cqrsBadRequest) else
    CqrsSetResultSuccessIf(Factory.Rest.Retrieve(ORMWhereClauseFmt,[],Bounds,
      fCurrentORMInstance),cqrsNotFound);
end;

My server is initialized as follows:

procedure InitHTTPService;
begin
  Model := CreateModel;
  uPhoDataLayer.InitializePhoModel(Model, DatabaseConnectionProperties);

  ServerDB := TSQLRestServerDB.Create(Model, ChangeFileExt(ExeVersion.ProgramFileName, '.db3'), False);
  ServerDB.CreateMissingTables;
  ServerDB.ServiceRegister(TPhoMessartCommand, [TypeInfo(IPhoMessartCommand)], sicShared);
  ServerDB.ServiceContainer.InjectResolver([TPhoMessartCommandFactory.Create(ServerDB)], True);

  ServerHTTP := TSQLHttpServer.Create('7979', [ServerDB], '+', useHttpApiRegisteringURI);
  ServerHTTP.AccessControlAllowOrigin := '*'; // allow cross-site AJAX queries
end;

// constructor TPhoMessartCommandFactory looks like
constructor TPhoMessartCommandFactory.Create(ARest: TSQLRest; AOwner: TDDDRepositoryRestManager = nil);
begin
  inherited Create(IPhoMessartCommand, TPhoMessartCommand, TMessart, aRest, TPhoMessart, aOwner);
  AddFilterOrValidate(['*'], TSynFilterTrim.Create);
  AddFilterOrValidate(['Name'], TSynValidateNonVoidText.Create);
end;

On the client side I have the following code:

var
  Model: TSQLModel;
  Client: TSQLHttpClient;
  MessartCommand: IPhoMessartCommand;
begin
  Model := TSQLModel.Create([]);
  Client := TSQLHttpClient.Create('localhost','7979', lModel);

  Client.ServiceDefine([IPhoMessartCommand], sicShared);
  Client.Services.Resolve(IPhoMessartCommand, MessartCommand);

  if not Assigned(MessartCommand) then
    raise Exception.Create('Can''t get PhoMessartCommand')
  else
    MessartCommand.SelectByName('somemessart');
end;

and if I call MessartCommand.SelectByName('somemessart'), I am getting this access violation because of Factory is nil.

Is it a bug or am I doing something wrong?

Thx for your help!

#83 mORMot 1 » how to consume a WCF service » 2015-06-11 11:39:44

cypriotcalm
Replies: 1

Hello Arnaud,

I would like to consume a WCF service (SOAP based with a WSDL file). I also have an address http://localhost:59997/HostDevServer/HelloWorldService.svc. I also know that this service offers a method Add(int a, int b).

1. How do I consume this service with mORMot and with a WSDL? I have imported the WSDL file and have an interface. How do I use it on the client side?
2. How do I consume such service if I don't have any WSDL file, but I know the method name and its signature?

Could you provide some sample code or point me to an appropriate sample project if there is one?

I have tried something like this, but without success:

var
  aModel: TSQLModel;
  aClient: TSQLRestClientURI;
begin
  aModel := TSQLModel.Create([], 'http://localhost:59997/HostDevServer/HelloWorldService.svc');
  aClient := TSQLRestClientURI.Create(aModel);
  ShowMessage(aClient.CallBackGetResult('Add', ['a',3,'b',2]));
  ...
end

Thank you!

#84 Re: mORMot 1 » using the history feature » 2015-05-21 09:12:39

ab wrote:

You have to call VirtualTableExternalRegister() only for the history table, which is TSQLRecordHistory by default.
Or call VirtualTableExternalRegisterAll which will make all tables externals.

But honestly, the history may gain to be in the local SQlite3, since performance would probably be much higher.

I can read the history from the local DB without problems!

But I would like to save the history in the external production db where the data is modified by multiple users.

Is there a possibility to turn on/off the "TrackChanges" at runtime?

Is a feature request worth also to save in the history who changed a data record?

#85 Re: mORMot 1 » interface inheritance at RegisterInterfaces internal error L2111 » 2015-05-21 05:50:43

Dear AB, could you help me with this factory? Because I am stuck with that! :-(

#86 Re: mORMot 1 » using the history feature » 2015-05-21 05:47:46

ab wrote:

From your code, it is created in the Sqlite3 local .db3 file, not in the external database.

Ah ok, thx, I will try to read the history from the local file.

But how can I create  the history table automatically by the mORMot in the external database? How would the code look like? Or should I create the table manually!?

#87 Re: mORMot 1 » using the history feature » 2015-05-20 14:12:23

ab wrote:

You do not have the History table in your main DB?

No, is it created automatically after calling "create missing tables"? What name should it get?

#88 mORMot 1 » using the history feature » 2015-05-20 12:31:49

cypriotcalm
Replies: 7

Hello Ab,

I have followed your instruction how to setup the history features but I don't see anywhere the history table in my database. My code is

  Model := TSQLModel.Create([TSQLRecordHistory, TPhoMessartSQLRecord], ROOT_NAME);

  VirtualTableExternalRegister(Model, TPhoMessartSQLRecord, FPhoRepository.MDBConnectionProperties, 'messart');

  ServerDB := TSQLRestServerDB.Create(Model, ChangeFileExt(ExeVersion.ProgramFileName, '.db3'), False);

  ServerDB.TrackChanges([TPhoMessartSQLRecord]);

  ServerDB.CreateMissingTables;

  ClientDB := TSQLRestClientDB.Create(ServerDB);

Then I add some new data into the table

var
  I: Integer;
  MA: TPhoMessartSQLRecord;
begin
  MA := TPhoMessartSQLRecord.Create;
  try
    for I := 0 to 10 do
    begin
      MA.Name := IntToStr(I+1);
      ClientDB.Add(MA, True);
    end;
  finally
    MA.Free;
  end;
end;

I don't see in the database any history-table. What am I doing wrong?

#89 Re: mORMot 1 » interface inheritance at RegisterInterfaces internal error L2111 » 2015-05-20 11:42:46

cypriotcalm wrote:
ab wrote:

Is fPropsMapping nil ?

fPropsMapping should have been set by TPhoMessartCommandFactory.Create
Why is it not the case?

No, fPropsMapping is not nil, the variable Factory is nil in the following code:

function TDDDRepositoryRestCommand.ORMAdd(aAggregate: TObject): TCQRSResult;
begin
  if CqrsBeginMethod(qaCommandDirect,result) then begin   
    // Factory is nil
    Factory.AggregateToTable(aAggregate,0,fCurrentORMInstance);
    ORMPrepareForCommit(soInsert,aAggregate);
  end;
end;

Ab, how do I right set this factory?

#90 Re: mORMot 1 » interface inheritance at RegisterInterfaces internal error L2111 » 2015-05-20 06:28:16

ab wrote:

Is fPropsMapping nil ?

fPropsMapping should have been set by TPhoMessartCommandFactory.Create
Why is it not the case?

No, fPropsMapping is not nil, the variable Factory is nil in the following code:

function TDDDRepositoryRestCommand.ORMAdd(aAggregate: TObject): TCQRSResult;
begin
  if CqrsBeginMethod(qaCommandDirect,result) then begin   
    // Factory is nil
    Factory.AggregateToTable(aAggregate,0,fCurrentORMInstance);
    ORMPrepareForCommit(soInsert,aAggregate);
  end;
end;

#91 Re: mORMot 1 » interface inheritance at RegisterInterfaces internal error L2111 » 2015-05-20 05:15:24

ab wrote:

How did you initialize the factory?
Did you override the constructor as expected?

Yes, the overriden constructor of the factory looks like

constructor TPhoMessartCommandFactory.Create(ARest: TSQLRest; AOwner: TDDDRepositoryRestManager = nil);
begin
  inherited Create(IPhoMessartCommand, TPhoMessartCommand, TPhoMessart, aRest, TPhoMessartSQLRecord, aOwner);
  AddFilterOrValidate(['*'], TSynFilterTrim.Create);
end;

But, honestly speaking, I don't know where and whether I use to use right. Can you advise me a bit?

Should I create the constructor in the following initialization code? If I create the factory, I get the access violation error anyway :-(

var
  PhoMessartCommandFactory: TPhoMessartCommandFactory;
begin
  FPhoRepository := TPhoDBRepository.Create;

  Model := TSQLModel.Create([TPhoMessartSQLRecord], ROOT_NAME);

  VirtualTableExternalRegister(Model, TPhoMessartSQLRecord, FPhoRepository.MDBConnectionProperties, 'messart');
  Model.Props[TPhoMessartSQLRecord].ExternalDB.MapField('ID', 'IdMessart');

  ServerDB := TSQLRestServerDB.Create(Model, ChangeFileExt(ExeVersion.ProgramFileName, '.db3'), False);
  ServerDB.CreateMissingTables;
  ServerDB.ServiceDefine(TPhoMessartCommand, [IPhoMessartCommand], sicShared);

  // FACTORY
  PhoMessartCommandFactory := TPhoMessartCommandFactory.Create(ServerDB);

  ServerHTTP := TSQLHttpServer.Create('7979', [ServerDB], '+', useHttpApiRegisteringURI);
  ServerHTTP.AccessControlAllowOrigin := '*'; // allow cross-site AJAX queries
end;

#92 Re: mORMot 1 » interface inheritance at RegisterInterfaces internal error L2111 » 2015-05-19 15:23:06

Dear AB!

I have another problem! :-) I setup my service server as follows

var
  PhoMessartCommandFactory: TPhoMessartCommandFactory;
begin
  FPhoRepository := TPhoDBRepository.Create;

  Model := TSQLModel.Create([TPhoMessartSQLRecord], ROOT_NAME);

  VirtualTableExternalRegister(Model, TPhoMessartSQLRecord, FPhoRepository.MDBConnectionProperties, 'messart');
  Model.Props[TPhoMessartSQLRecord].ExternalDB.MapField('ID', 'IdMessart');

  ServerDB := TSQLRestServerDB.Create(Model, ChangeFileExt(ExeVersion.ProgramFileName, '.db3'), False);
  ServerDB.CreateMissingTables;
  ServerDB.ServiceDefine(TPhoMessartCommand, [IPhoMessartCommand], sicShared);

  ServerHTTP := TSQLHttpServer.Create('7979', [ServerDB], '+', useHttpApiRegisteringURI);
  ServerHTTP.AccessControlAllowOrigin := '*'; // allow cross-site AJAX queries
end;

Everything is ok and I can start the server. The I execute the following code

var
  cmd: IPhoMessartCommand;
  MA: TPhoMessart;
  macaption: RawUTF8;
  I: Integer;
begin
    ServerDB.Services.Resolve(IPhoMessartCommand, cmd);
    if Assigned(cmd) then
    begin
      MA := TPhoMessart.Create;
      try
        for I := 1 to 10 do begin
          UInt32ToUtf8(I, macaption);
          MA.Name := 'messart #'+macaption;
          if (cmd.Add(MA) <> cqrsSuccess) then
              raise Exception.CreateFmt('Invalid data: %s',[cmd.GetLastErrorInfo]);
        end;
         //here nothing is actually written to the database
        if (cmd.Commit <> cqrsSuccess) then
            raise Exception.CreateFmt('Commit error: %s',[cmd.GetLastErrorInfo]);
         // here everything has been written to the database
      finally
        MA.Free;
      end;
    end;
end;

And I get an access violation when accessing the statement "cmd.Add(MA)". This violation happens in the unit mORMotDDD in the statement "if fPropsMapping.MappingVersion<>fPropsMappingVersion then" in the following code:

// unit  mORMotDDD 

procedure TDDDRepositoryRestFactory.AggregateToTable(aAggregate: TObject; aID: TID; aDest: TSQLRecord);
var i: integer;
begin
  if fPropsMapping.MappingVersion<>fPropsMappingVersion then
    ComputeMapping;
  if aDest=nil then
    raise EDDDRepository.CreateUTF8(self,'%.AggregateToTable(%,%,%=nil)',
      [self,aAggregate,aID,fTable]);
  aDest.ClearProperties;
  aDest.IDValue := aID;
  if aAggregate<>nil then
    for i := 0 to high(fAggregateProp) do
      AggregatePropToTable(aAggregate,fAggregateProp[i],aDest,fAggregateToTable[i]);
end;

Do you have an idea what is happening here? :-)

#94 Re: mORMot 1 » interface inheritance at RegisterInterfaces internal error L2111 » 2015-05-19 14:04:30

ab wrote:

Did you register TPhoMessartObjArray via TJSONSerializer.RegisterObjArrayForJSON() ?

No!

But now I did it as follows and I don't get this annoying internal error anymore! Yeahaaa! :-) My code looks like:

type
  TPhoMessartObjArray = type TObjectDynArray;

  IPhoMessartQuery = interface(ICQRSQuery)
  ['{87BDB69C-485D-429C-A6AE-8E2751FFCFFB}']
    function SelectByName(const AName: String): TCQRSResult;
    function Get(out AAggregate: TPhoMessart): TCQRSResult;
    function GetAll(out AAggregates: TPhoMessartObjArray): TCQRSResult;
    function GetNext(out AAggregate: TPhoMessart): TCQRSResult;
    function GetCount: Integer;
  end;

  IPhoMessartCommand = interface(IPhoMessartQuery)
  ['{23BE1585-5ED9-4596-A507-A264A2FEE8F6}']
    function Add(const AAggregate: TPhoMessart): TCQRSResult;
    function Update(const AUpdatedAggregate: TPhoMessart): TCQRSResult;
    function Delete: TCQRSResult;
    function DeleteAll: TCQRSResult;
    function Commit: TCQRSResult;
    function Rollback: TCQRSResult;
  end;

implementation

initialization
  TJSONSerializer.RegisterObjArrayForJSON(TypeInfo(TPhoMessartObjArray), TPhoMessart);
  TInterfaceFactory.RegisterInterfaces([TypeInfo(IPhoMessartQuery), TypeInfo(IPhoMessartCommand)]);

Thx for your help! :-)

#95 Re: mORMot 1 » interface inheritance at RegisterInterfaces internal error L2111 » 2015-05-19 13:00:56

Thx for your links! The second one I havn't seen before!

I am still trying to find out the internal error. Now I have commented all the methods and step by step tested with wich methods the project can be compiled. And I have found out that that the method GetAll with my own custom data type causes the internal error.

   type
      TPhoMessartObjArray = type of TObjectDynArray;
   ...  
   ...
  IPhoMessartQuery = interface(ICQRSQuery)
  ['{CCFB5FAA-FFA2-437A-849C-E7F5A57763D6}']
    function SelectByName(const AName: String): TCQRSResult;
    function Get(out AAggregate: TPhoMessart): TCQRSResult;
//    function GetAll(out AAggregates: TPhoMessartObjArray): TCQRSResult;
    function GetNext(out AAggregate: TPhoMessart): TCQRSResult;
    function GetCount: Integer;
  end;

Do you have an idea why the internal error happens?


How do I define aggregates in a right way?

#96 Re: mORMot 1 » interface inheritance at RegisterInterfaces internal error L2111 » 2015-05-19 11:21:24

ab wrote:

Yes, it sounds right.
But it will also publish the services over REST/HTTP. Is it what you meant?

I am not sure yet, I hope so, I am new to all that stuff with SOA/Rest/OR-Mapping, I am learning by doing and reading ;-) What I want is to map my existing database by mORMot (data persistance), then I woud like to offer some services, e.g. "create/delete/modify a measurement art" (Domain model) and then publish these services (application). I try to follow http://synopse.info/files/html/Synopse% … #TITLE_552


Stop! :-) Maybe you are right and that's not what I want ;-)

I want actually to publish the services over REST/HTTP in a way they can be consumed by different clients (delphi, php, java etc.) So, I can't publish the delphi types, I have to do it with some kind of JSON parameters, don't I? Coud you give me a quick reference in the doc where I can read about it? It would be great!

#97 Re: mORMot 1 » interface inheritance at RegisterInterfaces internal error L2111 » 2015-05-19 09:37:30

A generally question, I would like a domain model with interface based services. Is this a right way how I am doing that... I have followed one of your DDD examples:

I define the interfaces (see the code above in the previous posts), then I register them in the initialization section.
After that I use them like follows:

  Model := TSQLModel.Create([], ROOT_NAME);
  ServerDB := TSQLRestServerDB.Create(Model,ChangeFileExt(ExeVersion.ProgramFileName,'.db3'), False);
  ServerDB.CreateMissingTables;
  ServerDB.ServiceDefine(TPhoMessartCommand, [IPhoMessartCommand], sicShared);

  ServerHTTP := TSQLHttpServer.Create('7979', [ServerDB], '+', useHttpApiRegisteringURI);
  ServerHTTP.AccessControlAllowOrigin := '*'; // allow cross-site AJAX queries

  ...  
  ...
  var
     cmd: IPhoMessartCommand;
  begin
    ServerDB.Services.Resolve(IPhoMessartCommand, cmd);
    if Assigned(cmd) then
    begin
      // do something
    end; 
  end;

#98 Re: mORMot 1 » index creation in the mORMot » 2015-05-19 09:05:38

Yes, I did check the doc ;-) But anyway thx for your help!

#99 Re: mORMot 1 » boolean data type created as int(11) in MySQL » 2015-05-19 09:03:46

Ok, thx for your answer, I also did solve the problem in this way, but I thought I could do it without custom SQL code and let it do the ORM for me

#100 Re: mORMot 1 » interface inheritance at RegisterInterfaces internal error L2111 » 2015-05-19 09:00:08

ab wrote:

I never saw this internal error L2111.
Which version of the compiler are you using?

Are you able to compile the regression tests, and the dddInfraRepoUser unit?

This is pretty weird...

I sadly do not know how to circumvent it.
Perhaps putting a "type" between the two definitions.
Or trying to define IPhoMessartCommand in a second unit...
Or changing the GUID...

I am using Delphi XE3.

I have never bone before with the regression tests. Which project in the library is that?

I have tested your assumptions but without success. If I remove the code (see below), I can compile the project. Otherwise, not :-(

 initialization
   TInterfaceFactory.RegisterInterfaces([TypeInfo(IPhoMessartQuery), TypeInfo(IPhoMessartCommand)]);

Board footer

Powered by FluxBB