You are not logged in.
Pages: 1
Hi. Still trying to figure out if I can use mORMot for my application...
Yes I know, Datasets are not welcome here. But I have the DevExpress components, essential to my client's need in terms of grouping, filtering, ...
I would like to use ORM principles and be able to connect some kind of dataset to a DevExpress cxGrid (cause they're really awesome). And because my application is really about showing data and creating reports. I really really need those grids.
I would like to be able to update the grid's underlying data, directly from the grid, which is possible only, AFAIK, with a TClientDataSet (which is writable).
How is it possible to send changes to the REST server through a TClientDataSet (concerning Post, Update, Delete).
For example, I can Post to a ClientDataSet, but changes are only local. And how can I refresh?
Offline
A problem I encountered is that when using a REST server, the TClientDataSet generated by ToClientDataSet cannot perform a Refresh. The standard grid controls call the TClientDataSet.Refresh procedure, which throws because there is no data provider defined.
For example, if I want a list of people, I have the server-side REST function like root/people. It calls some PersonRepository.FindAll function. The Client application would call this root/people REST method, get the JSON, encode it into a TClientDataSet. Then, the grid could display it. After a grid row has been modified by the user, the data is posted using the standard TClientDataSet.Post procedure. But how can the DataSet automatically call the server POST method to update a person information? And the PUT method to insert a new person and retrieve the new ID? What about the DELETE method?
Maybe I am missing something. But I don't see how to get rid of the client-side TDataSet features, as they are tightly coupled with many client visual components (cxGrid, cxTreeList, cxVerticalGrid...). And I don't see how to acheive this with current mORMot framework tools.
Offline
As a frequent user of mORMot and dataset, perhaps I can help you.
But first I have to understand your problem.
Yes, you cannot use Refresh.
But you can use all other features of datasets.
E.g. , I am using something like this:
procedure TSQLNewData.AfterPost(DataSet: TDataSet);
var
S:string;
begin
if PreventCircular then exit;
if RestClient=nil then exit;
S:='After post (update) of #'+InttoStr(DataRecord.ID)+' on '+DataRecord.SQLTableName;
if RestClient.Update(DataRecord) then
begin
UpdateLookup(DataSet);
S:=S + ' ok.';
end else S:=S + ' failure !';
if Assigned(FOnDebugInfo) then
FOnDebugInfo(S);
end;
procedure TSQLNewData.AfterNewRecord(DataSet: TDataSet);
var
S:string;
LF:TField;
LocalID:TID;
begin
if PreventCircular then exit;
if RestClient=nil then exit;
// Only add a record. Do not send any data. Just get a new record with the server generated ID
DataRecord.ClearProperties('*');
LocalID:=RestClient.Add(DataRecord,False);
if LocalID>0 then
begin
S:=' ok.';
// ok ... but be carefull for circular !!
LF:=DataSet.FindField('ID');
if LF<>nil then LF.AsLargeInt:=LocalID;
end
else
begin
S:=' failure !';
//Raise;
end;
S:='New record of #'+InttoStr(DataRecord.ID)+' on '+DataRecord.SQLTableName+S;
if Assigned(FOnDebugInfo) then
FOnDebugInfo(S);
end;
procedure TSQLNewData.BeforeCancel(DataSet: TDataSet);
var
S:string;
begin
if Assigned(FOnDebugInfo) then FOnDebugInfo('BeforeCancel');
if PreventCircular then exit;
if RestClient=nil then exit;
// Only delete the record when in insert mode, because the record is already added to the server at the beginning of insert
if DataSet.State=dsInsert then
begin
S:='Cancel (delete) of #'+InttoStr(DataRecord.ID)+' on '+DataRecord.SQLTableName;
if RestClient.Delete(DataRecord.RecordClass,DataRecord.ID) then
begin
DataRecord.ID:=0;
S:=S + ' ok.'
end
else
begin
S:=S + ' failure !';
end;
if Assigned(FOnDebugInfo) then
FOnDebugInfo(S);
end;
end;
procedure TSQLNewData.BeforeDelete(DataSet: TDataSet);
var
S:string;
begin
if PreventCircular then exit;
if RestClient=nil then exit;
S:='Delete of #'+InttoStr(DataRecord.ID)+' on '+DataRecord.SQLTableName;
if RestClient.Delete(DataRecord.RecordClass,DataRecord.ID) then
begin
UpdateLookup(DataSet);
DataRecord.ID:=0;
S:=S + ' ok.'
end
else
begin
S:=S + ' failure !';
end;
if Assigned(FOnDebugInfo) then
FOnDebugInfo(S);
end;
Offline
The TSynDBDataSet class, as defined in SynDBMidasVCL.pas, is able to push modifications to the SynDB instance, which may be a SynDBRemote access over HTTP.
But there is indeed no such feature yet for the REST / ORM approach.
I suspect what you expect is the http://synopse.info/fossil/tktview?name=3f9e5ffccf feature request.
"Allow to push modifications of a TClientDataSet into the server"
But I'm personnally more in favor of the MVC/MVVM model proposed by http://synopse.info/fossil/tktview?name=73c1da3c40
"UI automatic mapping (via "Mediator" or "MVVM" patterns)"
I've just updated this feature request to explicitly ask for TDataSet support, if possible - and linked to this forum discussion thread.
Offline
Hi AOG,
Thank you very much for your example! It will help. I will investigate this. Though, what are those PreventCircular all around?
AG
Thank you for your explanations.
I read the feature request ticket. Sounds interesting, even if I'm not sure I'm 100% aware of what it would look like. I'm more confused about UI "generation". I don't know how the framework could literally generate a UI. I saw it being done in the MainDemo though, but how could we use custom components (DevExpress, LMD, ...). Custom designs, templates, etc. IMHO, I think views should generally be managed by the developer, not by the framework.
Concerning the TDataSet.
In order to fulfill the n-tier architecture, I would have to move my logic from the database (where it currently resides) to the business-layer or application layer. But, since the TDataSet is the "core" of many my views, and it, the SynDBDataSet, (correct me if I'm wrong) is directly connected to the persistence layer, I'm not sure I understand how the business logic could be implemented in this case. While the dataset pushes the modifications to the SynDB instance, there is no context at all. The SynDB only receives an instruction "Update record ID 2 on table "People" set Name = "Joe" ...". Or "Select all the clients and return them as JSON". But it bypasses totally the business logic. (again, correct me if I'm wrong).
What i see possible, is a REST / ORM approach as you described. Like some datasets, there are SQL properties "SQLSelect, SQLUpdate, SQLInsert". Those could be converted to a REST approach, calling the REST methods.
TRestDataSet.SelectAction := ['GET','/root/getPeople']; // Would return 200 OK + People JSON
TRestDataSet.UpdateAction := ['POST', '/root/updatePerson']; (ID={ID},Name={Name}) // would return 200 OK ?
TRestDataSet.InsertAction := ['POST', '/root/addPerson']; (Name={Name}) // would return 200 OK & new id ?
TRestDataSet.DeleteAction := ['DELETE', '/root/deletePerson/{ID}']
Then, on the server side,
procedure RestServer.UpdatePerson(Response : THttpResonse; AID : TID, AName : RawUTF8, ABorn : TDateTime, ADied : TDateTime)
var
APerson : TPerson;
begin
try
APerson := PersonRepository.Find(ID);
if APerson = nil then raise Exception.Create('No Person found with this ID');
UserController.UpdatePerson(APerson, AName, ABorn, ADied);
APerson.Save;
Response := THttpResponse.Create(200);
except on (E : Exception)
Response := THttpResponse.Create(400);
end;
end;
procedure UserController.UpdatePerson(APerson : TPerson; AName : RawUTF8; ABorn, ADied : TDateTime)
begin
if ADied > ABorn then raise Exception.Create('A person cannot have a Died date posterior to a Born date unless is Zombie');
if APerson.Name = '' then raise Exception.Create('A Person's name cannot be empty');
with APerson do begin
Name := AName;
Born := ABorn;
Died := ADied;
end;
end;
And so on. The problem I see is defining the convention of what is returned by the server (in terms of calculated fields, or if there are more modifications on the server that the client does not know about). About exceptions...
I would like this feature, of course. I'm only proposing a kind of "prototype" feature. I think without the TDataSet being able to post to a REST server, I'm not sure I can use the framework for my application. At least, not until I figure out how to use the business logic behind the scenes, and how to use a DataSet will all of this. Maybe writing a TDataSet wrapper could solve the problem. AB started to to it. would like to help, but I'm just starting to understand the framework. It would take me months to understand what I am playing with here.
Last edited by ludydoo (2015-02-04 14:56:48)
Offline
@ludydoo
The code is part of a MVC setup. Its part of the controller.
It allows for dropping a datasource on the form. Dataware controls can be connected with this datasource.
The datasource is connected to the controller at runtime (which creates the datasets, based on TSQLRecord instances).
No further actions or code are needed.
All update / change / delete / insert actions are translated into mORMot ORM CRUD commands by this controller.
But, sometimes, data comes back from mORMot (e.g. a record ID from a new record).
The dataset is updated with this ID.
This triggers a CRUD action, that is unwanted (circular): if PreventCircular then exit;
Greetings, Alfred.
Offline
Pages: 1