Search code examples
delphidelphi-7delphi-xe7accuracerdb

How do you log a specific table in Accuracer?


Code like this logs all table inserts (from the entire application):

procedure TForm1.ACRDatabase1AfterInsertRecord(Sender: TACRDataSet;
  const TableName: WideString; const FieldValues: TACRArrayOfTACRVariant);
begin
if (AnsiUpperCase(TableName) = AnsiUpperCase(LogTable.TableName)) then
 Exit;
 if (Sender is TACRTable) then
 LogTable.Insert();
 LogTable.FieldByName('EventTime').AsDateTime := Now;
 LogTable.FieldByName('TableName').AsString := TableName;
 LogTable.FieldByName('EventType').AsString := 'Insert ';
 LogTable.FieldByName('Whatever').AsString := FieldValues[4].AsString;
 LogTable.Post();
end;

But fieldValues are different for each table so you might crash the application (almost sure) using fieldvalues i.e their index number.

How do you overcome this ? Is it possible to log each table separately ?


Solution

  • As I mentioned in a comment, I don't have Accuracer, but thought it might be helpful to post a generic method of doing client-side logging, which can capture the value of one or more fields and be used for as many datasets as you need. You may be able to use part of it in your ACRDatabase1AfterInsertRecord handler, as its Sender parameter appears to identify the dataset into which the new row has been inserted.

    As you can see, there is a LogFields procedure which can be included in the AfterInsert handler of any dataset you like and this calls a separate GetFieldsToLog procedure which adds the names of the field(s) to log for a given dataset to a temporary StringList. It's only the GetFieldsToLog procedure which needs to be adapted to the needs of a given set of datasets.

    procedure TForm1.GetFieldsToLog(ADataSet : TDataSet; FieldList : TStrings);
    begin
      FieldList.Clear;
      if ADataSet = AdoQuery1 then begin
        FieldList.Add(ADataSet.Fields[0].FieldName);
      end
      else
        // obviously, deal with other specific tables here
    end;
    
    procedure TForm1.LogFields(ADataSet : TDataSet);
    var
      TL : TStringList;
      i : Integer;
      ValueToLog : String;
    begin
      TL := TStringList.Create;
      try
        GetFieldsToLog(ADataSet, TL);
        for i := 0 to TL.Count - 1 do begin
          ValueToLog := ADataSet.FieldByName(TL[i]).AsString;
          //  do your logging here however you want
        end;
      finally
        TL.Free;
      end;
    end;
    
    procedure TForm1.ADOQuery1AfterInsert(DataSet: TDataSet);
    begin
      LogFields(DataSet);
    end;
    

    Btw, one of the points of having a separate GetFieldsToLog procedure is that it helps to extend client-side logging to changes in existing dataset records. If you generate this list at start-up and save it somewhere, you can use it in the BeforePost event of a dataset to pick up the current and previous values of the field (using its Value and OldValue properties), save those in an another StringList and log them in the AfterPost event. Of course, if you'e using a common store for these value from more than one dataset, you need to make sure that the AfterPost of one dataset fire before the BeforePost of any other, or do the logging entirely within the BeforePost (having to store the old and current field values between Before- and AfterPost is messy, and it would be better to do everything in the AfterPost, but unfortunately the OldValue is out-of-date by the time AfterPost occurs.

    Be aware that getting the OldValue requires the specific dataset type to correctly implement it. Not all types of dataset I've come across do, though, so it needs checking.

    Btw #2, supposing you have a procedure like this

    procedure TForm1.DoSomething(AnObject : TObject);
    

    then you can use "if AnObject is ..." to do something like this

    var
      AnAdoQuery : TAdoQuery;
    begin
      if AnObject is TAdoQuery then begin
        // First, use a cast to assign Sender to the local AnAdoQuery variable
        AnAdoQuery := TAdoQuery(AnObject);
        // Then, we can do whatever we like with it, e.g.
        Caption := AnAdoQuery.Name;
      end;
    end;
    

    Otoh, if for some reason (and I can't immediately think why we would want to but never mind) we just want to check that what we've been passed as the AnObject parameter is a particular object, we can omit the cast and just do

      if AnObject = AdoQuery1 then 
        ShowMessage('Received AdoQuery1');
    

    This equality check works, regardless of the actual class of what we've been passed as the AnObject parameter because all other classes are descendants of AnObject's declared class, namely TObject.