0

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 ?

user3927897
  • 611
  • 3
  • 10
  • 20
  • Do the elements of FieldValues have a tag property? If so, you could set the tag of the required element prior to the AfterInsertRecord call, then iterate over the elements looking for the marked one. – No'am Newman Oct 29 '14 at 04:12
  • TACRArrayOfTACRVariant is specific to Accuracer, which I don't have. However, if you know the field(s) you want to log for particular tables, you might find it more straightforward to do your logging in the tables' AfterInsert event. Need more explanation? – MartynA Oct 29 '14 at 11:13
  • @No'am Newman - yes,fields have a tag property but I dont know how to implement that.Never done such a thing ... – user3927897 Oct 29 '14 at 22:23

1 Answers1

0

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.

MartynA
  • 30,454
  • 4
  • 32
  • 73
  • is not "FieldList.Add(ADataSet.Fields[0].FieldName);" basically the same as "LogTable.FieldByName('Whatever').AsString := FieldValues[4].AsString;" ? – user3927897 Oct 29 '14 at 22:29
  • Indeed, they possibly are basically the same thing (I say "possibly" because I'm not entirely sure what your " [...] := FieldValues[4].AsString [...]" is intended to do). All I was trying to do was to suggest a way of addressing your problem that was a) generic, that is, not limited to Accuracer, nor to specific datasets in an application, b) easily maintainable, because any necessary changes can be concentrated in the GetFieldsToLog() routine and c) extensible in that it is easily extensible to things like logging changes to field values. [cont] – MartynA Oct 29 '14 at 22:44
  • ... btw, I could see from an earlier q of yours that you are already familiar with server-side triggers, so could presumably implement server-side logging based upon them if that were appropriate to the circs your q is about. – MartynA Oct 29 '14 at 22:46
  • No this is file-server database.It has no actual triggers but the database component can actually do a similar work.The code insert was taken from their help file.Is there way to redirect the sender i.e like "if (Sender is TACRTable1) then" ??? – user3927897 Oct 29 '14 at 22:49
  • The "if .. is" construct is certainly capable of testing whether the subject (Sender) is of a given *type*, e.g. TACRTable, but tbh I've never actually tried seeing whether it can verify that Sender is a specific *instance* of that type. Regardless, if the "if ..." constructs succeeds with the Sender's type, it ought to be trivial to add a test to check the identity of the Sender *instance* (to see whether it is TACTable1, f.i.). – MartynA Oct 29 '14 at 22:56
  • "it ought to be trivial to add a test to check the identity of the Sender instance (to see whether it is TACTable1" - mind showing me? I have a brain block today ... – user3927897 Oct 30 '14 at 02:01
  • Sure. I've added a bit of explanation at the end of my answer. Sorry if the whole thing is getting a bit long-winded. – MartynA Oct 30 '14 at 10:15