Automated History

Hello,

I’ve currently implemented the following Event to achieve an automated Log of changed records.

(Next to this “EventHandler” I’ve also set the fields Modficiation count (MCNT), Modfication User (MUSR) and Last Modification Timestamp (LMTS) to ServerAutoRefresh)

    procedure [TDataAbstractService].DataAbstractServiceBeforeProcessDeltas(aSender: TObject; aDeltaStructs: TDADeltaStructList);
var
  iUserID       : Integer;
  utcNow        : TUtcDateTime;
  iStructCnt    : Integer;
  tDelta        : TDADelta;
  sTable        : String;
  iChangeCnt    : Integer;
  dcDeltaChange : TDADeltaChange;
  tDataSet      : TDADataSet;
  sFLPX         : String;
  iMCNT         : Integer;

  dsDeltaStruct   : TDADeltaStruct;
  iDeltaLog       : IDADelta;
  bpDeltaLog      : TDABusinessProcessor;
  tDeltaChangeLog : TDADeltaChange;
  iCnt            : Integer;

begin
  iUserID := Session[ Global.SESSION_USERID ];
  utcNow  := Global.getUTC;

  for iStructCnt := aDeltaStructs.Count - 1 downto 0  do
  begin
    tDelta := aDeltaStructs[ iStructCnt ].Delta.GetDelta;
    sTable := tDelta.LogicalName;
    aDeltaStructs[ iStructCnt ].BusinessProcessor.RaiseExceptionAtError := true;
    dsDeltaStruct := nil;
    tDataSet := ServiceSchema.Datasets.FindDatasetByName( sTable );

    { TODO : THIS IS A NOT OPTIMAL --> TRY TO FIND THE FIELDS BY ITERATING OVER THEM AND USING LeftStr or so }
    sFLPX := tDataSet.Fields[ 0 ].Name.Substring( 0, 2 );
    for iChangeCnt := 0 to tDelta.Count - 1 do
    begin
      dcDeltaChange := tDelta.Changes[ iChangeCnt ];
      if ( dcDeltaChange.ChangeType = ctInsert ) or
         NOT Assigned( tDataSet.FindField( sFLPX + 'MCNT' ) ) then
        iMCNT := 0
      else
        iMCNT := VarToIntDef( dcDeltaChange.OldValueByName[ sFLPX + 'MCNT' ], -1 ) + 1;

      if NOT Assigned( dsDeltaStruct ) then
      begin
        iDeltaLog := NewDelta( 'ZZM_' + tDelta.LogicalName );
        for iCnt := 0 to tDataSet.Fields.Count - 1 do
          iDeltaLog.AddFieldName( tDataSet.Fields[ iCnt ].Name, tDataSet.Fields[ iCnt ].DataType );
        iDeltaLog.AddFieldName( sFLPX + 'LVTS', datLargeInt );

        for iCnt := 0 to tDelta.KeyFieldCount - 1 do
          iDeltaLog.AddKeyFieldName( tDelta.KeyFieldNames[ iCnt ] );
        iDeltaLog.AddKeyFieldName( sFLPX + 'LVTS' );

        //for this BP we also use the Owner of the original BP
        bpDeltaLog := TDABusinessProcessor.Create( aDeltaStructs[ iStructCnt ].BusinessProcessor.Owner );
        bpDeltaLog.Assign( aDeltaStructs[ iStructCnt ].BusinessProcessor );
        bpDeltaLog.ReferencedDataset      := iDeltaLog.LogicalName;

        //--> IF THIS IS SET I CAN'T RAISE AN EXCEPTION AGAIN --> THIS leads to an AV
        //bpDeltaLog.OnProcessError         := DABusinessProcessor1ProcessError;

        dsDeltaStruct := aDeltaStructs.Add( iDeltaLog, bpDeltaLog );
      end;

      //Update old Entry from Log...
      if dcDeltaChange.ChangeType <> ctInsert then
      begin
        tDeltaChangeLog := iDeltaLog.Add( iDeltaLog.Count, ctUpdate );
        tDeltaChangeLog.OldValues := dcDeltaChange.OldValues;
        tDeltaChangeLog.OldValueByName[ sFLPX + 'LVTS' ] := High( Int64 );

        tDeltaChangeLog.NewValues := dcDeltaChange.OldValues;
        tDeltaChangeLog.NewValueByName[ sFLPX + 'LVTS' ] := utcNow;
      end;

      //Apply Server-Changes...
      if Assigned( tDataSet.FindField( sFLPX + 'MCNT' ) ) then dcDeltaChange.NewValueByName[ sFLPX + 'MCNT' ] := iMCNT;
      if Assigned( tDataSet.FindField( sFLPX + 'MUSR' ) ) then dcDeltaChange.NewValueByName[ sFLPX + 'MUSR' ] := iUserID;
      if Assigned( tDataSet.FindField( sFLPX + 'LMTS' ) ) then dcDeltaChange.NewValueByName[ sFLPX + 'LMTS' ] := utcNow;


      //Write new Entry in Log...
      if dcDeltaChange.ChangeType <> ctDelete then
      begin
        tDeltaChangeLog := iDeltaLog.Add( iDeltaLog.Count, ctInsert );
        tDeltaChangeLog.NewValues := dcDeltaChange.NewValues;
        tDeltaChangeLog.NewValueByName[ sFLPX + 'LVTS' ] := High( Int64 );
      end;

    end;
  end;
end;

I just want to know:

  1. Do I have to think about possible problems regarding MultiThreading?
  2. What about the OnError-Handler for the BusinessProcessor:
    If I assign it I cannot raise the error for the client: in this case I get a Streaming Error resulting in an AV

Best regards,
Peter

it depends on used class factory. If default class factory is used, no problems will be present.

also you can use threadvar variables.

it is known issue in pascal/delphi, but you can raise a new exception of the same class like

  raise Exception(Error.NewInstance).Create(Error.Message);

Hello Evgeny and thanks for clarification,

however I’m just curious about the the class factory: why does it depend on them? Just because of the “Owner”? Because every other variable should remain on the thread’s stack and so every instance should have it’s one “memory window”.

So what makes the default (create per request, if I remember correctly) so special?

If you think about the “Global”: they just contain atomic functions which I need all around the programm and they don’t modify any “global” variables, so this shouldn’t be the reason for any problem…
Opposite to some examples I, however, declare my schema within the Main Data Form of the Service-Project, so there’s only one instance of this - when theres minor load I didnt face only problems until now.

Thanks,
Peter

from Class Factories:
The class factories provided with RemObjects SDK include:

  • Per-request instantiation - a new service instance will be created for each incoming request, and destroyed afterwards (default).
  • Singleton - a single service instance will be used to serve all calls (with different synchronization options for thread safety)
  • Pooling - a pool of instances will be maintained to server incoming requests
  • Per-Client instantiation - every client will be served by a distinct service instance

so services created with Singleton or Pooling class factories can share their private variables with all clients and it can be a security hole. services created with default implementation (Per-request instantiation) will have no such hole.

Problems can be arisen if you use advanced business logic, where is possible to modify schema on-the-fly via server-side scripts, etc