DisableControls breaks master/detail linkage

Been having a strange problem with cascade deletes and have tracked it down to a call to DisableControls on the master dataset.

Basically I’m iterating through the master dataset, deleting certain records. As I move through the master records, the detail dataset updates to show the relevant details for the selected master. A call to Delete on the master then produces a delete delta on the master and a delete delta for each detail record. When I’ve finished I have a complete delta with all relevant deletes for both master and detail.

If I use DisableControls on the master first however, it prevents the detail table from changing. As I step through the records in the master, the detail table doesn’t update and remains showing the same detail records rather than those for the new master. The call to Delete then generates delete deltas for the wrong detail records.

This can’t be right can it? Surely the master/detail linkage between the two tables should work regardless of whether any bound controls are disabled (or even exist for that matter). Do I have something configured incorrectly here?

what MasterOptions and DetailOptions are set for master and detail tables?

Both tables have the same settings, specifically:

MasterOptions: [moCascadeOpenClose,moCascadeApplyUpdates,moCascadeDelete]
DetailOptions: [dtCascadeOpenClose,dtCascadeApplyUpdates,dtAutoFetch,dtIncludeInAllInOneFetch,dtDisableFetchForClonedTables]

Basically the defaults but with DetailOptions.dtDisableLogOfCascadeDeletes disabled.

I’ve emailed a small example project to support@remobjects.com which demonstrates the issue I’m having.

If you run this then you’ll see you can select either of the two master records (either directly in the grid or using the two Select buttons) and the detail automatically updates to show the relevant detail records. You can click the Show Current Records button to list the current master and detail records in the memo control.

Now, if you click the Disable Master Controls button, it stops working properly. If you use the Select buttons to change the active master record, then click the Show Current Records button, you’ll see that the current master is indeed changing but the detail records don’t update and can thus show the incorrect records.

You don’t even need the grids bound to the data sources. You can remove the link or the grids entirely and the problem persists. It appears that calling DisableControls on the master table is what causes the problem.

I’m not sure what’s going on here to be honest.

It’s the standard behavior of TDataset. when DisableControls is called, no dataset events were passed to datasource. as a result, TMasterDataLink doesn’t receive correspondent event.

call-stack when DisableControls isn’t called:

uDAMemDataset.TDAMemoryDataset.MasterChanged($34A39D0)
Data.DB.TMasterDataLink.RecordChanged(???)
Data.DB.TDataLink.DataSetChanged
Data.DB.TDataLink.DataSetScrolled(???)
Data.DB.TDataLink.DataEvent(???,???)
Data.DB.TDataSource.NotifyLinkTypes(deDataSetScroll,0,False)
Data.DB.TDataSource.NotifyDataLinks(deDataSetScroll,0)
Data.DB.TDataSource.DataEvent(deDataSetScroll,0)
Data.DB.TDataSet.DataEvent(deDataSetScroll,0)
Data.DB.TDataSet.MoveBy(???)
Data.DB.TDataLink.MoveBy(1)

when DisableControls is called, TDataSet.DataEvent doesn’t pass events to datasources:

procedure TDataSet.DataEvent(Event: TDataEvent; Info: NativeInt);
..
  NotifyDataSources := not (ControlsDisabled or (State = dsBlockRead));
..
  if NotifyDataSources then
  begin
    for I := 0 to FDataSources.Count - 1 do
      FDataSources[I].DataEvent(Event, Info);  //<<<< this method isn't called

You can emulate changing of master’s position (when DisableControl is used) with reassigning MasterSource:

procedure TfrmMain.Button1Click(Sender: TObject);
begin
  tblDetail.MasterSource:=nil;                 //added
  tblDetail.MasterSource:=dsmaster;            //added

  Memo1.Lines.Add(Format('Master: Id:%d',[tblMaster.FieldByName('Id').AsInteger]));
  tblDetail.First;
  while not tblDetail.EOF do
    begin
      Memo1.Lines.Add(Format('    Detail: Id:%d, MasterId:%d, Name:%s',[tblDetail.FieldByName('Id').AsInteger,tblDetail.FieldByName('MasterId').AsInteger,tblDetail.FieldByName('Name').AsString]));
      tblDetail.Next;
    end;
end;

Right I’m with you. I suspected it was because of the standard TDataSet behaviour and that calling DisableControls prevented the messages on which the linkage depends.

I was trying to think of a workaround as we often need to iterate through a master dataset which is bound to interface controls and thus use DisableControls to prevent unnecessary interface updates until the iteration is complete. Reassigning MasterSource on the detail to force a refresh is an interesting approach.

I guess there’s no other way around it really and just something we need to be aware of.