Title: Blob
Post by: cdhaene on September 09, 2017, 08:05:14 AM
Hi, I'm trying to make an uploadfunction using RTC. Aim is to upload sqlite-db from a smartphone to my server. Download works fine, but how to pass a blob parameter to my function? So far, I'm having this, but it doesn't work. with RtcClientModule1 do begin StartCalls; try with Data.NewFunction('f_initRTCClientDataset') do begin asLargeInt['clientdataset'] := nativeUint(aClientDataset); asString['datamod'] := aDataModuleName; asString['query'] := aQueryName; if aFixedSQL <> '' then asString['fixedsql'] := aFixedSQL else asString['sql'] := asql; lParams := NewArray('Params'); NewByteStream('stream'); asByteStream['stream'].CopyFrom(aStream, aStream.Size); asByteStream['stream'].Seek(0, soFromBeginning); lParams.asValue[0] := asByteStream['stream']; if length(arParams) > 0 then begin for i:= low(arParams) to high(arParams) do begin lParams.asValue[i+1] := arParams[i]; end; end; asBoolean['open'] := false; asBoolean['execute'] := true; astream.SaveToFile(ModeSelectionForm.PathToInternalStorage + 'Database/DATATEST.DB'); end; call(r_initRTCClientDataset); finally post; waitforCompletion; end; end;
The blob is the problem. Can you help me? Many thanks! Christophe
Title: Re: Blob
Post by: D.Tkalcec (RTC) on September 09, 2017, 10:21:18 AM
I see from your code that you are using CopyFrom() two times on "aStream". Once to copy "aStream" into asByteStream['stream'] and then again using the same Stream with SaveToFile. Is your "aStream" at position 0 before using CopyFrom() for the 1st time and are you moving "aStream" back to position 0 again before reading from "aStream" again for the 2nd time?
If that doesn't solve your problem, then please post your code used on the Client to populate the Stream, as well as the code you are using on the Server to read the Stream, since the problem could be on either side.
Best Regards, Danijel Tkalcec
Title: Re: Blob
Post by: cdhaene on September 10, 2017, 02:32:24 PM
Danijel, still no result... At client-side, on the smartphone I have this code now: procedure TRTCControl.RTCUpload(aClientDataset : TClientDataSet; aDataModuleName, aQueryName, aSql: string; arParams : array of variant; aStream : TMemoryStream ; aFixedSQL : string=''; aOpenDataset : boolean = false; aExecuteDataSet : boolean = False); var i : integer; lParams : TrtcArray; begin aClientDataSet.Close;
aClientDataset.RemoteServer := nil; aClientDataset.ProviderName := ''; with RtcClientModule1 do begin StartCalls; try with Data.NewFunction('f_initRTCClientDataset') do begin asLargeInt['clientdataset'] := nativeUint(aClientDataset); asString['datamod'] := aDataModuleName; asString['query'] := aQueryName;
if aFixedSQL <> '' then asString['fixedsql'] := aFixedSQL else asString['sql'] := asql;
lParams := NewArray('Params'); if length(arParams) > 0 then begin for i:= low(arParams) to high(arParams) do begin lParams.asValue[i] := arParams[i]; end;
end; NewByteStream('stream'); aStream.Seek(0, soFromBeginning); asByteStream['stream'].CopyFrom(aStream, aStream.Size); asByteStream['stream'].Seek(0, soFromBeginning); asBoolean['open'] := false; asBoolean['execute'] := true; aStream.Seek(0, soFromBeginning); astream.SaveToFile(ModeSelectionForm.PathToInternalStorage + 'Database/DATATEST.DB'); end; call(r_initRTCClientDataset); finally post;
waitforCompletion; end; end; end;
At server side, this code : procedure TServerContainer.f_initRTCClientDatasetExecute(Sender: TRtcConnection; Param: TRtcFunctionInfo; Result: TRtcValue); var rtcDataset : TrtcDataset; arParams : array of variant; i : integer; myQuery : TUniQuery;
// start local function assignMyQuery // Search the TuniQuery-component in all datamodules of the application function assignMyQuery(aDatamod, aQuery : string) : TUniQuery; var i : integer; j : integer; begin Result := nil; for i:= 0 to application.ComponentCount-1 do begin if application.Components[i] is TDataModule then begin if lowercase(TDataModule(application.Components[i]).Name) = lowercase(aDataMod) then begin for j:= 0 to application.Components[i].ComponentCount-1 do if application.Components[i].Components[j] is TUniQuery then begin if lowercase(TUniQuery(application.Components[i].Components[j]).Name) = lowercase(aQuery) then exit(TUniQuery(Application.Components[i].Components[j])); end; end; end; end; end; // end local function assignMyQuery
// function GetQuery(AQuery: String): string; begin with qGetQuery do begin Close; Params[0].AsString := AQuery; Open; Result := FieldByName('SQL').AsString; end; end;
begin try myQuery := assignmyQuery(Param.asText['datamod'], Param.asText['query']); if myQuery = nil then begin dbLog('ERROR',format('Error ! : The query %s on datamodule %s doesnot exist !',[Param.asText['query'], Param.asText['datamod']])); addLineToLog(format('Error ! : The query %s on datamodule %s doesnot exist !',[Param.asText['query'], Param.asText['datamod']])); Result.NewException; end else begin myQuery.close; if Param.asText['fixedsql'] <> '' then begin myQuery.sql.Text := Param.asText['fixedsql']; end else if Param.asText['sql'] <> '' then begin myQuery.sql.Text := getQuery(Param.asText['sql']); end;
if (Param.asArray['params'].Count) > 0 then for i:= 0 to Param.asArray['params'].Count-1 do begin myQuery.params[i].value := Param.asArray['params'][i]; end;
if Param.asBoolean['open'] = True then begin myQuery.active := True; DelphiDataSetToRtc(myQuery, result.NewDataSet); end else if Param.asBoolean['execute'] = True then begin myQuery.execute; Result.asBoolean := True; end; end; except on E: Exception do begin dbLog('ERROR', format('exception ! %s %s',[E.ClassName, E.Message])); AddLineToLog(format('exception ! %s %s',[E.ClassName, E.Message])); result.asException := 'exception !'; end; end; end;
Title: Re: Blob
Post by: D.Tkalcec (RTC) on September 10, 2017, 03:40:26 PM
Since you've stored the contents from your "aStream" in the 'stream' parameter of your remote function call object (using NewByteStream('stream') and then asByteStream['stream'] on the TRtcFunctionInfo object returned from Data.NewFunction...), you should be using Param.asByteStream['stream'] somewhere from the OnExecute event on the Server to access the contents of that Stream, but ... I don't see you doing that anywhere in the Server-side code you have posted above.
Best Regards, Danijel Tkalcec
Title: Re: Blob
Post by: cdhaene on September 10, 2017, 11:34:50 PM
Hi, in fact, the real trouble is that I don't know how to pass a blob to a remote function. I try to execute an insert query to insert a record in to my remote BD. this query posts a tiny sqlite-db that was produced by my android-app. The upload executes a query having as first parameters a blob (that sqlite-db) On my android device I'm having this now: procedure TRTCControl.RTCUpload(aClientDataset : TClientDataSet; aDataModuleName, aQueryName, aSql: string; arParams : array of variant; aStream : TMemoryStream ; aFixedSQL : string=''; aOpenDataset : boolean = false; aExecuteDataSet : boolean = False); var i : integer; lParams : TrtcArray; begin aClientDataSet.Close;
aClientDataset.RemoteServer := nil; aClientDataset.ProviderName := ''; with RtcClientModule1 do begin StartCalls; try with Data.NewFunction('f_UploadFileToServer') do begin asLargeInt['clientdataset'] := nativeUint(aClientDataset); asString['datamod'] := aDataModuleName; asString['query'] := aQueryName;
if aFixedSQL <> '' then asString['fixedsql'] := aFixedSQL else asString['sql'] := asql;
lParams := NewArray('Params');
NewByteStream('stream'); asByteStream['stream'].CopyFrom(aStream, aStream.Size); asByteStream['stream'].Seek(0, soFromBeginning); lParams.asValue[0] := asByteStream['stream']; <----------THIS ONE DOESN'T COMPILE
if length(arParams) > 0 then begin for i:= low(arParams) to high(arParams) do begin lParams.asValue[i+1] := arParams[i]; end; end;
asBoolean['open'] := false; asBoolean['execute'] := true; astream.SaveToFile(ModeSelectionForm.PathToInternalStorage + 'Database/DATATEST.DB'); end; call(r_initRTCClientDataset); finally post;
waitforCompletion; end; end; end;
Title: Re: Blob
Post by: D.Tkalcec (RTC) on September 11, 2017, 07:36:06 AM
Delphi does NOT automatically convert a TMemoryStream into a Variant, which is why the line of code you've commented "does NOT compile". But ... you are already passing the contents of your "aStream:TStream" variable to your remote function as a "stream" parameter with this code ... with RtcClientModule1 do begin with Data.NewFunction('f_UploadFileToServer') do begin NewByteStream('stream'); // create a new Stream object, to be sent as 'stream' parameter of your remote function aStream.Seek(0, soFromBeginning); // make sure your source stream (aStream in this case) is at the beginning asByteStream['stream'].CopyFrom(aStream, aStream.Size); // copy the contents of "aStream" to your 'stream' parameter end; Call(r_initRTCClientDataset); end;
... which could also be written without using "with", to make it clear what is being called where ... RtcClientModule1.Prepare('f_UploadFileToServer'); // create a new TRtcFunctionInfo object to be used with RtcClientModule1 aStream.Seek(0, soFromBeginning); // make sure your source stream (aStream in this case) is at the beginning RtcClientModule1.Param.NewByteStream('stream'); // create a new Stream object to be sent as 'stream' parameter of the remote function RtcClientModule1.Param.asByteStream['stream'].CopyFrom(aStream, aStream.Size); // copy the contents of "aStream" to your 'stream' parameter RtcClientModule1.Call(r_initRTCClientDataset);
Anyway ... that's not the problem. What you are missing is the Server-side code to extract that stream. You can access that 'stream' parameter on the Server from the "OnExecute" event of the TRtcFunction object and copy the 'stream' contents into any other "aStream:TStream" variable like this ... if Param.isType['stream']=rtc_ByteStream then // make sure that 'stream' parameter actually has a rtcByteStream begin Param.asByteStream['stream'].Seek(0,soFromBeginning); // move source stream to the beginning aStream.CopyFrom(Param.asByteStream['stream'],Param.asByteStream['stream'].Size); // copy contents from 'stream' parameter to 'aStream' object end;
PS. The name of the variable is 'stream' here, only because you've used it in your example, but it can be anything else. You can also send the contents of Streams inside Arrays, but then you should use the NewByteStream method on the TRtcArray object (or TRtcRecord or TRtcDataSet or any other object that can hold multiple elements) instead of directly creating it on the TRtcFunctionInfo object (which was, in your example above, returned from Data.newFunction('f_UploadFileToServer') and used in the "with" block). Best Regards, Danijel Tkalcec
Title: Re: Blob
Post by: cdhaene on September 11, 2017, 07:47:39 AM
hooo. Difficult to understand... So, server-side, I'll have this code, is that correct? procedure TServerContainer.f_UploadFileToServerExecute(Sender: TRtcConnection; Param: TRtcFunctionInfo; Result: TRtcValue); var rtcDataset : TrtcDataset; arParams : array of variant; i : integer; myQuery : TUniQuery; aStream : TStream;
function assignMyQuery(aDatamod, aQuery : string) : TUniQuery; var i : integer; j : integer; begin Result := nil; for i:= 0 to application.ComponentCount-1 do begin if application.Components[i] is TDataModule then begin if lowercase(TDataModule(application.Components[i]).Name) = lowercase(aDataMod) then begin for j:= 0 to application.Components[i].ComponentCount-1 do if application.Components[i].Components[j] is TUniQuery then begin if lowercase(TUniQuery(application.Components[i].Components[j]).Name) = lowercase(aQuery) then exit(TUniQuery(Application.Components[i].Components[j])); end; end; end; end; end;
function GetQuery(AQuery: String): string; begin with qGetQuery do begin Close; Params[0].AsString := AQuery; Open; Result := FieldByName('SQL').AsString; end; end;
begin try myQuery := assignmyQuery(Param.asText['datamod'], Param.asText['query']); if myQuery = nil then begin dbLog('ERROR',format('Error ! : The query %s on datamodule %s doesnot exist !',[Param.asText['query'], Param.asText['datamod']])); addLineToLog(format('Error ! : The query %s on datamodule %s doesnot exist !',[Param.asText['query'], Param.asText['datamod']])); Result.NewException; end else begin myQuery.close; if Param.asText['fixedsql'] <> '' then begin myQuery.sql.Text := Param.asText['fixedsql']; end else if Param.asText['sql'] <> '' then begin myQuery.sql.Text := getQuery(Param.asText['sql']); end;
if (Param.asArray['params'].Count) > 0 then begin for i:= 0 to Param.asArray['params'].Count-1 do begin if Param.isType['stream']=rtc_ByteStream then begin aStream := TStream.Create; Param.asByteStream['stream'].Seek(0,soFromBeginning); aStream.CopyFrom(Param.asByteStream['stream'],Param.asByteStream['stream'].Size); Param.asByteStream['stream'] := aStream; aStream.Free; end else myQuery.params[i].value := Param.asArray['params'][i]; end; end;
if Param.asBoolean['open'] = True then begin myQuery.active := True;
DelphiDataSetToRtc(myQuery, result.NewDataSet); end else if Param.asBoolean['execute'] = True then begin myQuery.execute; Result.asBoolean := True; end; end; except on E: Exception do begin dbLog('ERROR', format('exception ! %s %s',[E.ClassName, E.Message])); AddLineToLog(format('exception ! %s %s',[E.ClassName, E.Message])); result.asException := 'exception !'; end; end; end;
Title: Re: Blob
Post by: D.Tkalcec (RTC) on September 11, 2017, 08:12:23 AM
You are making things more complicated than they are. On the Server, the "Param" object is basically the same object you have created by calling Data.newFunction() or Prepare() on the Client. I think the main source of misunderstanding here is the use of "with" statements, which make it unclear which methods are being called on which objects. So ... let's start by removing all "with" statements on the Client, to make it clear what you are actually doing. This code on the Client ... with RtcClientModule1 do begin with Data.NewFunction('f_UploadFileToServer') do begin NewByteStream('stream'); // create a new Stream object, to be sent as 'stream' parameter of your remote function aStream.Seek(0, soFromBeginning); // make sure your source stream (aStream in this case) is at the beginning asByteStream['stream'].CopyFrom(aStream, aStream.Size); // copy the contents of "aStream" to your 'stream' parameter end; Call(r_initRTCClientDataset); end;
... is equivalent to this (if we remote all "with" statements) ... // create a new TRtcFunctionInfo object to be used with RtcClientModule1. RtcClientModule1.Data.NewFunction('f_UploadFileToServer');
{ Since the object created above is a Remote Function, it is accessible here using the "RtcClientModule1.Param" property ... }
// Create a new ByteStream object as a 'stream' parameter of the remote function object we have just created above ... RtcClientModule1.Param.NewByteStream('stream');
// Copy the contents of "aStream:TStream" to our 'stream' object ... aStream.Seek(0, soFromBeginning); // move source stream to the beginning RtcClientModule1.Param.asByteStream['stream'].CopyFrom(aStream, aStream.Size); // copy the "aStream" contents
// Call the remote function ... RtcClientModule1.Call(r_initRTCClientDataset);
Now, all you have to know is that "RtcClientModule1.Param" object on the Client will be sent to the Server with the "Call" method and the same object will then be accessible on the Server using the "Param" object passed to the "OnExecute" event. So ... if you are using the code above to send a single Stream to the Server, then you should use something like this to access that Stream from the "OnExecute" event on the Server ... // make sure that 'stream' parameter actually has a rtcByteStream if Param.isType['stream']=rtc_ByteStream then begin // move source stream to the beginning Param.asByteStream['stream'].Seek(0,soFromBeginning); // copy contents from 'stream' parameter to 'aStream' object aStream.CopyFrom(Param.asByteStream['stream'],Param.asByteStream['stream'].Size); end;
As you can see, there is no Array here. And why should there be an Array, if you have created your 'stream' as a normal parameter of your remote function? Even though you are sending some of your parameters in the "params" array in your example code above, the "stream" object is NOT part of that array, since you have created it directly on the remote function object and is therefor passed on the same level as the "params" array and all the other parameters you are passing to the remote function (like "sql", "fixedsql" and so on). The fact that it's a Stream does NOT make it any different from any other data type you are passing as parameters. Best Regards, Danijel Tkalcec
Title: Re: Blob
Post by: cdhaene on September 11, 2017, 08:36:30 AM
I understand your array-remark. But, the query I execute has 7 parameters, so it's an array containing 7 parameters. The first on (index 0) is the blob
Title: Re: Blob
Post by: cdhaene on September 11, 2017, 10:01:11 AM
Danijel, don't want to be annoying but I can't make it work... Sorry Is there smething I don't see? Something I'm missing? At serverside I got a exception ! EWriteError Stream write error Client-code: procedure TRTCControl.RTCUpload(aClientDataset : TClientDataSet; aDataModuleName, aQueryName, aSql: string; arParams : array of variant; aStream : TMemoryStream ; aFixedSQL : string=''; aOpenDataset : boolean = false; aExecuteDataSet : boolean = False); var i : integer; lParams : TrtcArray; begin aClientDataSet.Close;
aClientDataset.RemoteServer := nil; aClientDataset.ProviderName := ''; with RtcClientModule1 do begin StartCalls; try with Data.NewFunction('f_UploadFileToServer') do begin asLargeInt['clientdataset'] := nativeUint(aClientDataset); asString['datamod'] := aDataModuleName; asString['query'] := aQueryName;
if aFixedSQL <> '' then asString['fixedsql'] := aFixedSQL else asString['sql'] := asql;
lParams := NewArray('Params');
NewByteStream('stream'); asByteStream['stream'].CopyFrom(aStream, aStream.Size); asByteStream['stream'].Seek(0, soFromBeginning); //lParams.asValue[low(arParams)] := asByteStream['stream'];
if length(arParams) > 0 then begin for i:= low(arParams) to high(arParams) do begin lParams.asValue[i+1] := arParams[i]; end; end;
asBoolean['open'] := false; asBoolean['execute'] := true; astream.SaveToFile(ModeSelectionForm.PathToInternalStorage + 'Database/DATATEST.DB'); end; call(r_initRTCClientDataset); finally post;
waitforCompletion; end; end; end;
Server-code : procedure TServerContainer.f_UploadFileToServerExecute(Sender: TRtcConnection; Param: TRtcFunctionInfo; Result: TRtcValue); var rtcDataset : TrtcDataset; arParams : array of variant; i : integer; myQuery : TUniQuery; aStream : TStream;
function assignMyQuery(aDatamod, aQuery : string) : TUniQuery; var i : integer; j : integer; begin Result := nil; for i:= 0 to application.ComponentCount-1 do begin if application.Components[i] is TDataModule then begin if lowercase(TDataModule(application.Components[i]).Name) = lowercase(aDataMod) then begin for j:= 0 to application.Components[i].ComponentCount-1 do if application.Components[i].Components[j] is TUniQuery then begin if lowercase(TUniQuery(application.Components[i].Components[j]).Name) = lowercase(aQuery) then exit(TUniQuery(Application.Components[i].Components[j])); end; end; end; end; end;
function GetQuery(AQuery: String): string; begin with qGetQuery do begin Close; Params[0].AsString := AQuery; Open; Result := FieldByName('SQL').AsString; end; end;
begin try myQuery := assignmyQuery(Param.asText['datamod'], Param.asText['query']); if myQuery = nil then begin dbLog('ERROR',format('Error ! : The query %s on datamodule %s doesnot exist !',[Param.asText['query'], Param.asText['datamod']])); addLineToLog(format('Error ! : The query %s on datamodule %s doesnot exist !',[Param.asText['query'], Param.asText['datamod']])); Result.NewException; end else begin myQuery.close; if Param.asText['fixedsql'] <> '' then begin myQuery.sql.Text := Param.asText['fixedsql']; end else if Param.asText['sql'] <> '' then begin myQuery.sql.Text := getQuery(Param.asText['sql']); end;
if (Param.asArray['params'].Count) > 0 then begin for i:= 0 to Param.asArray['params'].Count-1 do begin if Param.isType['stream']=rtc_ByteStream then begin aStream := TStream.Create; Param.asByteStream['stream'].Seek(0,soFromBeginning); aStream.CopyFrom(Param.asByteStream['stream'],Param.asByteStream['stream'].Size); aStream.Seek(0, soFromBeginning) ; myquery.Params[i].AsStream := aStream; aStream.Free; end else myQuery.params[i].value := Param.asArray['params'][i]; end; end;
if Param.asBoolean['open'] = True then begin myQuery.active := True;
DelphiDataSetToRtc(myQuery, result.NewDataSet); end else if Param.asBoolean['execute'] = True then begin myQuery.execute; Result.asBoolean := True; end; end; except on E: Exception do begin dbLog('ERROR', format('exception ! %s %s',[E.ClassName, E.Message])); AddLineToLog(format('exception ! %s %s',[E.ClassName, E.Message])); result.asException := 'exception !'; end; end; end;
Title: Re: Blob
Post by: D.Tkalcec (RTC) on September 11, 2017, 10:24:51 AM
If you want to have the ByteStream inside your 'params' Array at element 0, you should call the NewByteStream(0) method on the Array object (which would be "lParams" in your example above). So, here is that code for the Client (I'm avoiding the use of the "with" statement here for clarity reasons) ...
procedure TRTCControl.RTCUpload(<...snip...> arParams : array of variant; aStream : TMemoryStream; <...snip...>); var i : integer; lParams : TRtcArray; begin { the next line is similar to calling RtcClientModule1.Data.NewFunction('f_UploadFileToServer'), but it also clears the "Data" object, in case they it was left assigned, which could happen in case of an exception while preparing any previous remote function call ... } RtcClientModule1.Prepare('f_UploadFileToServer');
{ the next line creates an Array as the "Params" parameter for the remote function and is basically the exact same thing as what you did in your example code, but uses the "Param" object on the RtcClientModule instead of using the "with" statement to imply access to the TRtcFunctionInfo object created with a call to Data.NewFunction() ... } lParams := RtcClientModule.Param.NewArray('Params');
{ the next line creates a ByteStream in the "Params" Array at index 0, which is what you actually whated to do, but have instead created a ByteStream as a 'stream' parameter of the remote function object using NewByteStream('stream') ... } lParam.NewByteStream(0);
{ the next 2 lines move the source "aStream" to the beginning and then copy the contents of the "aStream" into the ByteStream ... } aStream.Seek(0,soFromBeginning); lParam.asByteStream[0].CopyFrom(aStream, aStream.Size);
{ the rest of your Client-side code was correct from the start ... } if length(arParams) > 0 then for i:= low(arParams) to high(arParams) do begin lParams.asValue[i+1] := arParams[ i ]; end; <...snip...>
Now, you have the Stream at index 0 of your 'params' array (and your other parameters at indexes 1 and above), so you could do something like this on the Server to read it all ...
var lArray:TRtcArray; // temp variable for our "params" array aStream:TStream; // wherever we want the Stream begin if Param.isType['params']=rtc_Array then // make sure we have received an Array begin lArray:=Param.asArray['params']; if lArray.Count > 0 then for i:= 0 to lArray.Count-1 do if lArray.isType[ i ]=rtc_ByteStream then begin // aStream := ?????? <---- YOUR CODE COMES HERE
{ <---- HERE, you need to set "aStream" to your destination Stream! Since you have sent some data to the Server, I guess you know what you want to do with it. The stream contents is here and below is the code to copy it into any other TStream, but copying it into some TStream which you are just going to destroy makes no sense. Anway ... Here is to code which would copy that Stream to "aStream" (which you have to initialize before) ... }
lArray.asByteStream[ i ].Seek(0,soFromBeginning); aStream.CopyFrom(lArray.asByteStream[ i ],lArray.asByteStream[ i ].Size); end else <SomethingThatExpectsYourVariantValue> := lArray.asValue[ i ]; end; end;
Best Regards, Danijel Tkalcec
Title: Re: Blob
Post by: cdhaene on September 11, 2017, 12:50:50 PM
Danijel,
the purpose of my call is the execution of a query. This query inserts a record in a table on the database (MySQL). the first field is a blob (the stream) containing a small sqlite-db.
the query that will be executed is
INSERT INTO MyTable (bbfw, status, pandaid, sequentienr, naam, resource, versie, bbfw_id) VALUES (:P0, :P1, :P2, :P3, :P4, :P5, :P6, :P7)
:P0 is the stream
The server-side has to execute this query, inserting a record in a db.
Title: Re: Blob
Post by: D.Tkalcec (RTC) on September 11, 2017, 12:56:27 PM
In that case, you should definitely take a closer look at Database Access Demos included in the "Demos/DB_Access" folder and check the "rtcDB.pas" unit, which includes functions for copying data to-and-from a TDataSet, including Blob and Graphical fields. There are also example functions for the Server to prepare and execute a Query using parameters received from the Client (also including BLOB fields).
For example, the "BDEDemoSrv.pas" unit is using "rtcPrepareSqlWhere" and "rtcSetSqlWhereParams" functions from the "rtcDB.pas" unit to execute SELECT statements based on the SQL text and parameters received from the Client, and ... using "rtcPrepareSqlAction" and "rtcSetSqlActionParams" functions to prepare and execute INSERT, DELETE and UPDATE statements on the Server, also using SQL with parameters.
I'm not saying that you should copy/paste that code or even use these functions, but I think you should analyze these functions to see how they work. Then, you can write your own to do exactly what you want.
Best Regards, Danijel Tkalcec
Title: Re: Blob
Post by: cdhaene on September 11, 2017, 01:23:56 PM
I'll take a closer look at the DB-Demos. As far as I understand, this should do the trick at serverside: lArray:=Param.asArray['params'];
if (lArray.Count > 0) then begin for i:= 0 to lArray.Count-1 do begin if (Param.isType['stream']=rtc_ByteStream) then begin lArray.asByteStream[i].Seek(0,soFromBeginning); MyQuery.Params[i].LoadFromStream(lArray.asByteStream[i], ftBlob); end else MyQuery.Params[i+1].Value := lArray[i]; end; end;
Title: Re: Blob
Post by: D.Tkalcec (RTC) on September 11, 2017, 02:11:48 PM
You probably mean like this (see my comments) ... lArray:=Param.asArray['params'];
if (lArray.Count > 0) then begin for i:= 0 to lArray.Count-1 do begin if (lArray.isType[i]=rtc_ByteStream) then // <--- I've changed this line! begin lArray.asByteStream[i].Seek(0,soFromBeginning); MyQuery.Params[i].LoadFromStream(lArray.asByteStream[i], ftBlob); end else MyQuery.Params[i].Value := lArray[i]; // <--- and changed this line! end; end; Best Regards, Danijel Tkalcec
Title: Re: Blob
Post by: HelgeLange on September 11, 2017, 05:56:58 PM
Sorry if I get into the discussion, I use UniDAC (same as the OP) all the time with RTC and I send blobs another way. I load a UniQuery as TVirtualTable (comes with UniDAC and also is a free component) and I save the TVirtualTable as one bytestream in RTC, it contains all rows selected by the original query with as many blobs as you like. When the function is called and executed on the server side I take the bytestream and load it into a VirtualTable there. From there you an use a batchmove to insert it into your server database or whatever. I place the code AS IT IS here and you can extrapolate what you need : Client side: // here I only send a DataId, which will be returned and where I can identify which data was asked for // then a SQL query, that way I can execute whatever I want // also which database I want to query (my server can handle multiple DBs) // LU is a datetime for LastUpdate so I can filter only data that was changed since the last time I checked procedure TRtcClient.DoSyncronizeFromServer(DataId: Integer); var FI : TRtcFunctionInfo; LU : TDateTime; begin FI := cmMobile.Prepare('MobileSyncServerTables'); FI.asInteger[RTC_PARAM_DATA_ID] := DataId; FI.asString[RTC_PARAM_SQL_TEXT] := __Internal_GetSQL(DataId, LU); FI.asString[RTC_PARAM_FIELD_DATABASE] := RemoteDataBase; FI.asDateTime[RTC_PARAM_LAST_UPDATE] := LU; cmMobile.Call(Result_SyncServerTable); end;
// the object VT is from type TVirtualTable // so when i get a rtc_Record and it has data (_Result.asBoolean[RTC_PARAM_HAS_DATA]) then I load the ByteStream into the VirtualTable // and call __Internal_ReceiveSyncData which processes these data // and afterwards I call all the listener to tell them that something has changed procedure TRtcClient.Result_SyncServerTableReturn(Sender: TRtcConnection; Data, Result: TRtcValue); var _Result : TRtcRecord; DataId : Integer; aStream : TMemoryStream; begin If Result.isType = rtc_Record Then begin _Result := Result.asRecord; If _Result.asBoolean[RTC_PARAM_HAS_DATA] Then begin VT.Open; aStream := TMemoryStream(_Result.asByteStream[RTC_PARAM_VTDATA]); aStream.Position := 0; VT.LoadFromStream(aStream); DataId := _Result.asInteger[RTC_PARAM_DATA_ID]; // VT.SaveToFile('F:\ServerData_' + DataId.ToString + '.vdb'); __Internal_ReceiveSyncData(DataId, VT, _Result.asDateTime[RTC_PARAM_LAST_UPDATE]); VT.Close; CallListener(DataId); end; end; end;
Serverside : procedure TDMBRS.fctSyncServerTableExecute(Sender: TRtcConnection; Param: TRtcFunctionInfo; Result: TRtcValue); var _Result : TRtcRecord; bStream : TStream; begin {$IFDEF USE_CODESITE}CodeSite.TraceMethod( Self, Self.ClassName + '.fctSyncServerTableExecute' );{$ENDIF} ConnectionTimer.Enabled := False; FdtLastUsed := Now; _Result := Result.NewRecord; _Result.asDateTime['LU'] := Now; // define this as the last check point {$IFDEF USE_CODESITE} CodeSite.Send(csmLevel4, 'LastUpdate', FormatDateTime(FormatSettings.ShortDateFormat, Param.asDateTime['LU']) + ' ' + FormatDateTime(FormatSettings.ShortTimeFormat, Param.asDateTime['LU'])); {$ENDIF} _Result.asBoolean['HasData'] := False; {$IFDEF USE_CODESITE} CodeSite.Send(csmLevel4, 'SQL', Param.asString['SQL']); {$ENDIF} If Not DBConnection.Connected Then DBConnection.Connected := True; If Not ReadTransaction.Active Then ReadTransaction.StartTransaction; QR.SQL.Text := Param.asString['SQL']; QR.ParamByName('LU').AsDateTime := Param.asDateTime['LU']; try QR.Open; except on E: Exception do begin {$IFDEF USE_CODESITE} CodeSite.Send(csmLevel1, 'Error', E.Message); {$ENDIF} ConnectionTimer.Enabled := True; raise Exception.Create(Self.ClassName + '.fctSyncServerTableExecute ERROR: ' + #13#10 + E.Message); end; end; {$IFDEF USE_CODESITE} CodeSite.Send(csmLevel4, 'RecordCount', QR.RecordCount); {$ENDIF} If QR.RecordCount > 0 Then begin bStream := _Result.NewByteStream('vtData'); if VT.Active then VT.Close; VT.Open; VT.Assign(QR); VT.SaveToStream(bStream, True, True); VT.Close; _Result.asBoolean['HasData'] := True; _Result.asInteger['DataId'] := Param.asInteger['DataId']; end; QR.Close; ConnectionTimer.Enabled := True; end;
on serverside the interesting part start in the Try...Except block with the query itself, it's a simple TUniQuery. I send the query from the client to be executed. If there are date (If QR.RecordCount > 0 Then) Then I create a new ByteStream, I load the query into the VirtualTable and the VT saves it to the ByteStream The same way it works when you upload to the server, just make the query, VirtualTable.Assign also copies the content of any blob and send the stream :) I hope that helps
Title: Re: Blob
Post by: cdhaene on September 11, 2017, 08:50:17 PM
Thanks! I'll give it a try right away
|