You are not logged in.
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! :-)
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?
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 emptyHow 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!
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!
Thank you!
why is it not possible to serialize interfaces?
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;
...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.
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*
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,...)
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;"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;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....
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 objectIf 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!!!
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=qwertyand I do this then I get the situation like stated in my first post. Please read it again!
Simple call aMessarts.Free after use?
If it would be so simple ;-) after the call .free I get an "EInvalidPointer"-error :-(
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=qwertyIf 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 :-(
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! :-)
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?!
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! :-)
Great! It works! Thank you very much for your fast answer!
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;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
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?
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!
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?
try this?
function TPhoMessartCommand.Select: TCQRSResult; begin Result := ORMSelectAll('', []); end
I have already tried it out before, but it didn't work
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;There should still be something wrong in your code.
How is your TPhoMessartCommand class defined?
Arnaud, you are right!
This time it's my mistake ![]()
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);
(...)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! ![]()
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;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.
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 ![]()
Is the reference to the factory not done with the followiing code?
ServerDB.ServiceContainer.InjectResolver([TPhoMessartCommandFactory.Create(ServerDB)], True);I'll look further and come back.
Ok, thx, I'll wait
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?
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?
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!
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]));
...
endThank you!
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?
Dear AB, could you help me with this factory? Because I am stuck with that! :-(
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!?
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?
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?
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?
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;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;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? :-)
Yep! Changed! Thx!
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! :-)
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?
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!
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;Yes, I did check the doc ;-) But anyway thx for your help!
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
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)]);