I've built a windows service to sinchronize to Firebird databases.
I've built a windows service to sinchronize to Firebird databases.
A sample method to sync 2 tables is as follows:
function TCSVSync.SyncStoc(const aID: Integer; var mID: Integer; const aAction: String): Boolean;
begin
Result := False;
if aID <= -1 then Exit;
mID := -1;
if qSlaveStocs.Active then qSlaveStocs.Close;
try
qSlaveStocs.Params.ParamByName('ID').AsInteger := aID;
dbSlave.StartTransaction;
qSlaveStocs.Open();
if qMasterStocs.Active then qMasterStocs.Close;
qMasterStocs.CachedUpdates := True;
qMasterStocs.SQL.Text := ' update or insert into STOC (IDOF,IDVAL,VAL) values (:IDOF,:IDVAL,:VAL) matching (IDOF, IDVAL) returning ID ';
if (qSlaveStocs.RecordCount>0)and(not qSlaveStocs.Eof) then begin
try
dbm.StartTransaction;
try
qMasterStocs.Prepare;
qMasterStocs.Params.ParamByName('IDOF').Value := qSlaveStocs.FieldByName('IDOF').Value;
qMasterStocs.Params.ParamByName('IDVAL').Value := qSlaveStocs.FieldByName('IDVAL').Value;
qMasterStocs.Params.ParamByName('VAL').Value := qSlaveStocs.FieldByName('VAL').Value;
qMasterStocs.Open;
dbm.Commit;
if (not qMasterStocs.Eof) then
mID := qMasterStocs.FieldByName('ID').AsInteger;
except
on E: EFDDBEngineException do begin
dbM.Rollback;
end;
on E: Exception do
end
finally
qMasterStocs.Close
end;
end;
Result := True
finally
if qSlaveStocs.Active then qSlaveStocs.Close;
dbSlave.Commit
end
end;
Some times I get a error that can not detect why if it should not be: Service failed on execute: [FireDAC][Phys][FB]invalid transaction handle (expecting explicit transaction start).
Other times I get the error: [FireDAC][DApt]-400. Update command updated [0] instead of [1] record. Possible reasons: update table does not have PK or row identifier, record has been changed/deleted by another user.
Event if all tables have PK's and if try to execute the same code again it works.
What do I do wrong?
The second question is how to keep service running if any errors occurs?
Here is the main execution method:
procedure TCSVSync.ServiceExecute(Sender: TService);
var
Count,SecBetweenRuns: Integer;
begin
SecBetweenRuns := FSyncPeriod * 60;
Count := SecBetweenRuns; //:= 0;
while not Terminated do
begin
Inc(Count);
if Count >= SecBetweenRuns then
begin
Count := 0;
try
if (not DataConnected) then begin
DataConnect;
if DataConnected then begin
SyncStoc;
end
end;
finally
DataDisconnect
end;
end;
ServiceThread.ProcessRequests(false)
end
end;
A sample method to sync 2 tables is as follows:
function TCSVSync.SyncStoc(const aID: Integer; var mID: Integer; const aAction: String): Boolean;
begin
Result := False;
if aID <= -1 then Exit;
mID := -1;
if qSlaveStocs.Active then qSlaveStocs.Close;
try
qSlaveStocs.Params.ParamByName('ID').AsInteger := aID;
dbSlave.StartTransaction;
qSlaveStocs.Open();
if qMasterStocs.Active then qMasterStocs.Close;
qMasterStocs.CachedUpdates := True;
qMasterStocs.SQL.Text := ' update or insert into STOC (IDOF,IDVAL,VAL) values (:IDOF,:IDVAL,:VAL) matching (IDOF, IDVAL) returning ID ';
if (qSlaveStocs.RecordCount>0)and(not qSlaveStocs.Eof) then begin
try
dbm.StartTransaction;
try
qMasterStocs.Prepare;
qMasterStocs.Params.ParamByName('IDOF').Value := qSlaveStocs.FieldByName('IDOF').Value;
qMasterStocs.Params.ParamByName('IDVAL').Value := qSlaveStocs.FieldByName('IDVAL').Value;
qMasterStocs.Params.ParamByName('VAL').Value := qSlaveStocs.FieldByName('VAL').Value;
qMasterStocs.Open;
dbm.Commit;
if (not qMasterStocs.Eof) then
mID := qMasterStocs.FieldByName('ID').AsInteger;
except
on E: EFDDBEngineException do begin
dbM.Rollback;
end;
on E: Exception do
end
finally
qMasterStocs.Close
end;
end;
Result := True
finally
if qSlaveStocs.Active then qSlaveStocs.Close;
dbSlave.Commit
end
end;
Some times I get a error that can not detect why if it should not be: Service failed on execute: [FireDAC][Phys][FB]invalid transaction handle (expecting explicit transaction start).
Other times I get the error: [FireDAC][DApt]-400. Update command updated [0] instead of [1] record. Possible reasons: update table does not have PK or row identifier, record has been changed/deleted by another user.
Event if all tables have PK's and if try to execute the same code again it works.
What do I do wrong?
The second question is how to keep service running if any errors occurs?
Here is the main execution method:
procedure TCSVSync.ServiceExecute(Sender: TService);
var
Count,SecBetweenRuns: Integer;
begin
SecBetweenRuns := FSyncPeriod * 60;
Count := SecBetweenRuns; //:= 0;
while not Terminated do
begin
Inc(Count);
if Count >= SecBetweenRuns then
begin
Count := 0;
try
if (not DataConnected) then begin
DataConnect;
if DataConnected then begin
SyncStoc;
end
end;
finally
DataDisconnect
end;
end;
ServiceThread.ProcessRequests(false)
end
end;
I don't know about Firebird but for the second question you can set the service to restart automatically or the following code:
ReplyDeletewhile not Terminated do
begin
try
// your code
except
on E: Exception do
//write exception into the log
end;
ServiceThread.ProcessRequests(false)
end
Cristian Peța I do have exception handling inside methods (e.g. in Syncstoc)
ReplyDeleteR Gosp but probably something outside your try except block fails.
ReplyDeleteFor example: dbSlave.Commit
Also what if DataConnect trows an exception? The service will simply close.
ReplyDeleteAttila Kovacs autocommit is off
ReplyDeleteCristian Peța a error will log with connection failure and later try again, here it's ok
ReplyDeleteCachedUpdates in a service? Why?
ReplyDeleteUse: http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Managing_Transactions_(FireDAC)#Continuous_Transactions Firebird requires an open transaction at all times.
ReplyDeleteDany Marmur Just for a testing purpose
ReplyDeleteFred S. Not necessary.
ReplyDeleteR Gosp I havn't used FireDAC that much but in UniDAC I have seen this error 'invalid transaction handle' when I didn't use 'Retaining'.
ReplyDeleteFred S. Using CommitRetaining and RollbackRetaining does not close the transaction but must be closed because each method (such as Syncstoc) works on each table in the slave and master database.
ReplyDeleteAlthough, I'll check this out more thoroughly.