1

I have a Delphi program with 3 successive nested (master-details) FireDAC datasets namely: SuperMaster, Master and Details dataset.

In the AfterScroll event of the Details dataset only, new records are inserted into the database.

My program logic needs the AfterScroll event of the Details dataset to fire only once: when any of the parent datasets (SuperMaster or Master) scrolled to another record. The AfterScroll event should not trigger more than once because this will cause my logic to insert wrong records.

The problem is that when scrolling occurs to any of the parent datasets (from the current record to another one, even to the adjacent one), i.e. moving a single record only, the Details dataset AfterScroll event fired more than once and sometimes more than twice!

I do not want to mess with the original FireDac classes by overriding its AfterScroll event to force it to do my logic only once, nor to add a Boolean flag to my logic so that it runs only once.

I tried to put all the tables into one view, but it costs me to change the program logic and make future updates more demanding.

I searched and read many articles and a book ("Delphi in Depth: FIREDAC" by Cary Jensen), but could not find a solution nor know why or how it occurs!

Why does this happen? I want to understand the basics please. Is there any elegant solution?

AmigoJack
  • 5,234
  • 1
  • 15
  • 31
HasDev
  • 65
  • 6
  • Does [`.ControlsDisabled`](https://supportcenter.devexpress.com/ticket/details/q50565/) help your logic? – AmigoJack Jul 17 '21 at 11:07
  • AmigoJack, No it does not. – HasDev Jul 17 '21 at 12:48
  • You need to be ready for `AfterScroll` to be called multiple times. As well as when scrolling through the dataset it's called in all sorts of unexpected places, such as after a Post. – LachlanG Jul 18 '21 at 04:50

1 Answers1

1

Short of changing the FireDAC source (which I think would be a thoroughly bad idea - beware of opening Pandora's box) I don't think your goal of avoiding the use of a Boolean flag is achievable. It should however be trivial to achieve if you do use a Boolean flag.

I also wonder whether you observation that the Detail's AfterScroll event occurs more than once per detail dataset is correct, if that is what you are saying. If it is, you should edit your q to include a minimal, reproducible example, preferably based on the code below.

TDataSet and its descendants, including all the FireDAC datasets operate to a very tightly designed state machine which includes the handling of master-detail behaviour that's been tested by all the TDataSet usage since Delphi was first released. Trying to mess with that would invite disaster.

If you try the minimal VCL project below, I think you'll find it's not very hard to satisfy yourself that the Detail's AfterScroll event behaves exactly as you would expect from the coding of TDataSet's and FireDAC's source.

Running the code you will find that the breakpoint in DetailAfterScroll trips 4 times before the first breakpoint on MasterFirst. The next time the BP trips, observe the call stack via View | Debug Windows | Call stack; if you look down the cal stack, you'll find that the call to Detail.AfterScroll was ultimately called via the penultimate line of procedure TFDMasterDataLink.DataEvent(Event: TDataEvent; Info: NativeInt);

An FDMasterDataLink is automatically created to handle the triggering of Detail data events based on the operation of the Master. And that, really, is the end of the story. because however much you might disagree with this behaviour , you can't really do anything about it short of using a Boolean flag in your own code.

I think it would be wise to verify that DetailAfterScroll is only being called once per dataset that's on the detail side of the Master. If it's happening more than once, it would be worth checking that it isn't your own code (or linking together of DataSets) that's causing it.

As you'll see, nearly all of what the example does is defined in the OnCreate event to avoid having to use the Object Inspector to set up the components, so that is should "just work".

Code:

type
  TForm1 = class(TForm)
    Master: TFDMemTable;
    Detail: TFDMemTable;
    DataSource1: TDataSource;
    procedure FormCreate(Sender: TObject);
    procedure DetailAfterScroll(DataSet: TDataSet);
  public
    DetailsScrolled : Integer;
  end;
[...]

procedure TForm1.DetailAfterScroll(DataSet: TDataSet);
begin
  //  Place debugger breakpoint on the following line
  Inc(DetailsScrolled);
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  AField : TField;
begin
  AField := TIntegerField.Create(Self);
  AField.FieldName := 'MasterID';
  AField.DataSet := Master;
  Master.CreateDataSet;

  AField := TIntegerField.Create(Self);
  AField.FieldName := 'DetailID';
  AField.DataSet := Detail;

  AField := TIntegerField.Create(Self);
  AField.FieldName := 'MasterID';
  AField.DataSet := Detail;
  Detail.CreateDataSet;

  DataSource1.DataSet := Master;
  Detail.MasterSource := DataSource1;
  Detail.MasterFields := 'MasterID';
  Detail.IndexFieldNames := 'MasterID;DetailID';

  Master.InsertRecord([1]);
  Master.InsertRecord([2]);

  Detail.InsertRecord([1, 1]);
  Detail.InsertRecord([2, 1]);
  Detail.InsertRecord([3, 2]);

  //  Place debugger breakpoint on EACH of the following three lines
  Master.First;
  Master.Next;
  Master.First;
end;
MartynA
  • 30,454
  • 4
  • 32
  • 73
  • Dear MartynA, thank you very much for your effort in explaining part of the underhood FideDac work, your explanation took me to think more and trying every possible solution for my problem. The Datasets of my Program are of TFDQuery type, they are nested in consecutive (Master-Detail) fashion, I use SQL strings with Params in order to fill data in each one as the user scrolling through them. surprisingly when I remove the Params from the SQL and use SQL string like (Select * from ;) and depend only on MasterSource + master field properties, the situation seems to be MUCH better. Why??
    – HasDev Jul 18 '21 at 10:43
  • It is obvious that removing the Params from the SQL and uses SQL string like (Select * from ;) instead of (Select * from
    where = :;) and depend only on MasterSource + Masterfield properties will solve my problem. Now when I scroll in ANY of the Parent Datasets the Detail Dataset will fire its After Scroll event only Once. Only when scrolling in any of the parent datasets result in an empty detail dataset then it will fire twice! Needs Explanation Please. Thanks
    – HasDev Jul 18 '21 at 10:53