Lost records in datasnap REST client - delphi

I have a DataSnap/REST server and client application.
The server have a method to return one or more datasets with some number of records but, when this number of records is greater than 50, the first 50 records aren't received by the client application.
I have debugged the server application and the SQL statement is correct (I have executed it from IBExpert and returns the correct number of records).
I have used XE7, FireDAC and Firebird.
Into the server application I have this method to return a dataset
procedure TSvrMethodsMdl.AddTable(SQL, TabName: string; JSON: TFDJSONDataSets);
var
Q: TFDQuery;
begin
Q := TFDQuery.Create(nil);
try
Q.Connection := conTPV;
Q.Transaction := conTPV.Transaction;
Q.SQL.Text := SQL;
TFDJSONDataSetsWriter.ListAdd(JSON, TabName, Q);
finally
// FreeAndNil(Q);
end;
end;
Into the client application, for receiving records, I have this code
var
LDataSetList: TFDJSONDataSets;
LDataSet: TFDDataSet;
tTemp: TFDMemTable;
begin
....
// get remote data
LDataSetList := CliConnectMdl.SvrMethodsMdlClient.GetDataChanged(Shop, ResError);
// process data
LDataSet := TFDJSONDataSetsReader.GetListValueByName(LDataSetList, MyTabName);
tTemp.AppendData(LDataSet);
I think that is some of configuration, but I cant find what.
Any idea? Thanks

Related

How to return value from server to client in DataSnap projectgroup

I write datasnap application. I should know id of last uploaded record on server and after that upload new records from local db using cliendataset component.
I don't realise meaning of this code, what are there Parameters[0].Value, Parameters[1].Value and how it change correctly in order to return result from server:
FSelectCountryCommand.Parameters[0].Value.SetInt32();
FSelectCountryCommand.ExecuteUpdate;
Result := FSelectCountryCommand.Parameters[1].Value.GetInt32;
if I write manually random integer value, for example
result:=10; //FSelectCountryCommand.Parameters[1].Value.GetInt32;
then it works correctly and returns this value in ClientFormUnit.
My final goal is return result value(maxcountry_id) from Server to buttonclick procedure of ClientForm unit
Query on server:
select max(country_id) as maxcountry_id from country
ClientForm unit:
procedure Tclient.buttonCountryClick(Sender: TObject);
var c:integer;
begin
c:=Clientmodule1.ServerMethods1Client.SelectCountry;
Clientmodule1.lqCountry.close;
Clientmodule1.lqCountry.Params[0].AsInteger:= c;
Clientmodule1.lqCountry.close;
end;
ClientClassesUnit:
function TServerMethods1Client.SelectCountry:Integer;
begin
if FSelectCountryCommand = nil then
begin
FSelectCountryCommand := FDBXConnection.CreateCommand;
FSelectCountryCommand.CommandType :=
TDBXCommandTypes.DSServerMethod;
FSelectCountryCommand.Text := 'TServerMethods1.SelectCountry';
FSelectCountryCommand.Prepare;
end;
FSelectCountryCommand.Parameters[0].Value.SetInt32();
FSelectCountryCommand.ExecuteUpdate;
Result := FSelectCountryCommand.Parameters[1].Value.GetInt32;
end;
ServerMethodUnit:
function TServerMethods1.SelectCountry:Integer;
begin
qCountry.close;
qCountry.Open;
Result:= qCountryMaxCountry_id.AsInteger;
end;
I tried use global variable but without success: it is seen only in project area, not in project group area.
SOLUTION
this code works fine:
FSelectCountryCommand.ExecuteUpdate;
Result := FSelectCountryCommand.Parameters[0].Value.GetInt32;
As I understand, as select query doesn't contain parameters therefore this operator:
FSelectCountryCommand.Parameters[0].Value.SetInt32();
is redundant.

Delphi webserver broke after multiple calls at the same time

I have a simple webserver written in delphi which fetchs data from the DB and retrieve in JSON.
There are 20 endpoints, I will post one of them:
procedure TWM.WMactGruposGetAction(Sender: TObject; Request: TWebRequest;
Response: TWebResponse; var Handled: Boolean);
var
...
begin
// Get query fields
...
// Configurar resposta
Response.ContentType := APPLICATION_JSON + '; ' + CHARSET_UTF8;
// Look for grupos
qryGrupos := TFDQuery.Create(nil);
with qryGrupos do
begin
Connection := dmDados.GetConnection(NomeDB); // Open the
Active := False;
SQL.Clear;
Open('SELECT * FROM ' + T_PESSOAS_GRUPO +
' WHERE ' + C_ID_LOTEAMENTO + ' = ' + IDLoteamento);
if qryGrupos.RecordCount > 0 then
JsonArray := TJSONArray.Create;
try
First;
while not Eof do
begin
JsonObject := TJSONObject.Create;
CapturarCamposGrupos(JsonObject, qryGrupos);
JsonArray.AddElement(JsonObject);
Next;
end;
finally
Response.Content := JsonArray.ToString;
CloseQuery(qryGrupos); // Close and nil the query
dmDados.CloseConnection(NomeDB); // Close the TFDConnection
JsonArray.DisposeOf;
end;
end;
end;
function TdmDados.GetConnection(DBName: string): TFDConnection;
begin
FDConnection.Open();
Result := FDConnection
end;
This basically does the following:
Opens the TFDConnection
Opens the TFDQuery;
Closes the TFDQuery;
Closes the TFDConnection;
Note: The TFDConnection is created at design time when the application starts, so every API call uses the same TFDConnection;
Problem
The problem is that in my flutter app I call three endpoints at the same time (they pretty much do the same thing as this grupos one).
When I call each endpoint at a time it works fine, but when I go to this page that calls three endpoints at the same time. The webserver brokes.
The error messages differ but they were things like:
Lost connection to MySQL server
Access violation address at ...
Cannot connect to MySQL
And then the server never works more for any endpoint.
I feel like somehow the TFDConnection lost the connection and can not get it again. Any help?

How to return DataSet from DataSnap server?

Hello everyone who reads this! I hope you can help me with my problem, but if not, thank you for try. I have DataSnap server and client. DataSnap server methods can return to client a DataSet as function result. I'm getting data from MySQL DB with TFDQuery component. Somebody please help me to understand, how can I get a dataset from FDQuery component that already has data?
TDataSet.Data is OleVariant type property containing all data. But FDQuery doesn't have the same property. I need to return a dataset from FDQuery as OleVariant in function.
*Try, Except, FreeAndNil, DisposeOf etc removed from code for better understanding the problem
//Client side
procedure TForm1.GetDataSetFromServer;
var
Server: TServerMethods1Client;
DS: TClientDataSet;
begin
Server := TServerMethods1Client.Create(ClientModule1.SQLConnection1.DBXConnection);
DS := TClientDataSet.Create(nil);
DS.Data := Server.GetDataSet; //Call remote server method
end;
//DataSnap server side
function TServerMethods1.GetDataSet: OleVariant;
begin
FDQuery1.Close;
FDQuery1.SQL.Text := 'SELECT * FROM Table1';
FDQuery1.Open;
//Now i need to return all data as function result
result := ???
end;
Need any information that can be helpful. Thanks in advance! Have a nice day!
The simplest way to do this, AFAIK, is to add a TDataSetProvider, and also a TClientDataSet (if you don't already have one) to your Server module.
Then, you can modify your server code as follows:
function GetDataSet: OleVariant;
begin
if ClientDataSet1.Active then
ClientDataSet1.Close;
FDQuery1.Close;
FDQuery1.SQL.Text := 'SELECT * FROM Table1';
// FDQuery1.Open; Leave this to the DataSetProvider/ClientDataSet
//Now i need to return all data as function result
//result := ???
DataSetProvider1.DataSet := FDQuery1;
ClientDataSet1.ProviderName := 'DataSetProvider1';
ClientDataSet1.Open;
Result := ClientDataSet1.Data;
end;
The point of doing it this way is that TDataSetProvider has all the internal machinery necessary to package up its DataSet's data (i.e. FDQuery1's data) in a form that that can be sent between ClientDataSets. Incorporating the DataSetProvider in the server minimizes the code necessary in the client to consume the CDS data.
Btw, I'm assuming that your server module has the code necessary to 'export' GetDataSet as a server method.
You can also do return a TDataSet from the server function:
function TServerMethods1.GetDataSet: TDataSet;
begin
if ClientDataSet1.Active then
ClientDataSet1.Close;
FDQuery1.Close;
FDQuery1.SQL.Text := 'SELECT * FROM Table1';
DataSetProvider1.DataSet := FDQuery1;
ClientDataSet1.ProviderName := 'DataSetProvider1';
ClientDataSet1.Open;
Result := ClientDataSet1;
end;
For the client side, it now depends on what kind of server you are using.
If it is a DBX Datasnap Server, you will have to use a TsqlServerMethod (note the lower case) 'sql' here, and a TDataSetProvider with a TClientDataset, all preconfigured to retrieve the data from that Server.
If it is a REST Datasnap server, you can do this at the client:
procedure TfrmClientMain.btnRefreshClick(Sender: TObject);
var
Server: TServerMethods1Client;
lDataSet: TDataSet;
DSP: TDataSetProvider;
begin
Server := TServerMethods1Client.Create(ClientModule1.DSRestConnection1);
try
CDS.Close; // a TClientDataSet has been placed on the form
lDataSet := Server.GetDataSet();
DSP := TDataSetProvider.Create(Self);
try
DSP.DataSet := lDataSet;
CDS.SetProvider(DSP);
CDS.Open;
finally
CDS.SetProvider(nil);
DSP.Free;
end;
finally
Server.Free;
end;
end;

Delphi Soap WebServer doesn't allow more than 2 calls at a time

I have a problem with a SOAP WebServer: if I have a method on the server that takes a few seconds to give a result, and I can't make more than two calls "simoultaneously" (called from threads) from my client application (developed in Delphi Also - just for testing, the final client app will be web based). I mean that the threads are somehow serialized, and I don't know if it is supposed to happen like this or if I am doing something wrong, or if I need to make a certain setting.
Delphi XE2 Professional, Windows 7 64-bit
Link to my zipped project: http://1drv.ms/Nwu9nY
Server Method:
function TServiciu.Test: string;
var t0, t1, Duration : Cardinal;
Guid : tguid;
RGuid : HRESULT;
received : boolean;
const MaxTime : Cardinal = 3000;
begin
Result := '';
RGuid := CreateGuid(Guid);
if RGuid = S_OK then
Result := GuidToString(Guid);
t0 := GetTickCount;
t1 := GetTickCount;
duration := t1 - t0;
while (Duration < Maxtime) do begin
//waiting for something to be written in a db by another program
//I am doing querys to the database for that result
if received then begin
Result := Result + '_RECEIVED_' + DateTimeToStr(Now);
Break;
end;
t1 := GetTickCount;
duration := t1 - t0;
if duration > Maxtime then begin //MaxTime allowed is exceded
Result := Result + '_NOTRECEIVED_' + DateTimeToStr(Now);
Break;
end;
end;
end;
Client Procedure Execute of my thread:
procedure TThreadTest.Execute;
begin
try
// OleCheck will raise an error if the call fails
OleCheck(CoInitializeEx(NIL, COINIT_MULTITHREADED or COINIT_SPEED_OVER_MEMORY));
try
s := 'Calling Method Test; Result=' +
Self.ThGetService.Test;
Synchronize(self.ScrieRezultat); //write result in memo
finally
CoUninitialize;
FreeAndNil(self.ThHttprio);
end;
except
// We failed, do something
end;
end;
I am able to handle 40 concurrent calls with no trouble (actually more than that), but I'm using a different approach. My service is a CGI executable, so IIS manages the connections. When a SOAP request is made, IIS spins up a new instance of myapp_cgi.exe to handle the request. I top out at 40 TPS, but I am load-balanced across 4 servers so I can actually do 160 TPS, sustainable all day long, if necessary.
Hello I solved my problem, the thing was quite foolish, I had my server set up as HTTP 1.1.
Connections to a single HTTP 1.1 server are limited to two simultaneous connections.
http://support2.microsoft.com/kb/183110
I just had a very similar problem and I'm pretty sure your problem is on your client, not on your server.
See this topic here.

How to return Oracle Cursor from stored proc as Client Dataset using Delphi and DBExpress

1st off I am Still a little green to Delphi so this might be a "mundane detail" that's being over looked. [sorry in advance]
I have a need to create a TSQLDataset or TClientDataSet from an Oracle 11g cursor contained in a package. I am using Delphi XE2 and DBExpress to connect to the DB and DataSnap to send the data back to the client.
I'm having problems executing the stored procedure from the Delphi code.
Package Head:
create or replace
PACKAGE KP_DATASNAPTEST AS
procedure GetFaxData(abbr varchar2, Res out SYS_REFCURSOR);
END KP_DATASNAPTEST;
Package Body:
create or replace
PACKAGE body KP_DATASNAPTEST AS
procedure GetFaxData(abbr varchar2, Res out SYS_REFCURSOR)is
Begin
open Res for
SELECT Name,
Address1,
City,
fax_nbr
FROM name
JOIN phone on name.Abrv = phone.abrv
WHERE phone.fax_nbr is not null and name.abrv = abbr;
end;
END KP_DATASNAPTEST;
I have no problem executing this procedure in SQL Developer the problem resides in this code on the DataSnap server:
function TKPSnapMethods.getCDS_Data2(): OleVariant;
var
cds: TClientDataSet;
dsp: TDataSetProvider;
strProc: TSQLStoredProc;
begin
strProc := TSQLStoredProc.Create(self);
try
strProc.MaxBlobSize := -1;
strProc.SQLConnection:= SQLCon;//TSQLConnection
dsp := TDataSetProvider.Create(self);
try
dsp.ResolveToDataSet := True;
dsp.Exported := False;
dsp.DataSet := strProc;
cds := TClientDataSet.Create(self);
try
cds.DisableStringTrim := True;
cds.ReadOnly := True;
cds.SetProvider(dsp);
strProc.Close;
strProc.StoredProcName:= 'KP_DATASNAPTEST.GetFaxData';
strProc.ParamCheck:= true;
strProc.ParamByName('abbr').AsString:= 'ZZZTOP';
strProc.Open; //<--Error: Parameter 'Abbr' not found.
cds.Open;
Result := cds.Data;
finally
FreeAndNil(cds);
end;
finally
FreeAndNil(dsp);
end;
finally
FreeAndNil(strProc);
self.SQLCon.Close;
end;
end;
I have also tried assigning the param value through the ClientDataSet without any luck.
I would not be apposed to returning a TDataSet from the function if its easier or produces results. The data is used to populate custom object attributes.
As paulsm4 mentioned in this answer, Delphi doesn't care about getting stored procedure parameter descriptors, and so that you have to it by yourself. To get params of the Oracle stored procedure from a package, you can try to use the GetProcedureParams method to fill the list with parameter descriptors and with the LoadParamListItems procedure fill with that list Params collection. In code it might look like follows.
Please note, that following code was written just in browser according to documentation, so it's untested. And yes, about freeing ProcParams variable, this is done by the FreeProcParams procedure:
var
ProcParams: TList;
StoredProc: TSQLStoredProc;
...
begin
...
StoredProc.PackageName := 'KP_DATASNAPTEST';
StoredProc.StoredProcName := 'GetFaxData';
ProcParams := TList.Create;
try
GetProcedureParams('GetFaxData', 'KP_DATASNAPTEST', ProcParams);
LoadParamListItems(StoredProc.Params, ProcParams);
StoredProc.ParamByName('abbr').AsString := 'ZZZTOP';
StoredProc.Open;
finally
FreeProcParams(ProcParams);
end;
...
end;
I don't think Delphi will automagically recognize Oracle parameter names and fill them in for you. I think you need to add the parameters. For example:
with strProc.Params.Add do
begin
Name := 'abbr';
ParamType := ptInput;
Value := ZZZTOP';
...
end;

Resources