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