Why is BeforeProcessChange called twice?

I’ve just noticed something odd. I’m sending a delta to the server with a single change in it which is an Update.

Now, as this table is part of a master/detail hierarchy, my update rules are set to apply Inserts & Updates separately to Deletes, as is fairly standard.

What I’m seeing is that my BeforeProcessChange method on my TDABusinessProcessorRules derived class is being called twice. Here are the relevant bits of the call stack for each call:

ContactsServer.TCompanyServerRules.BeforeProcessChange($36E67D0,ctUpdate,$37AACE8,False)
uDABusinessProcessor.TDABusinessProcessor.DoBeforeProcessChangeEvent($37AACE8,False,True)
uDABusinessProcessor.TDABusinessProcessor.ProcessDelta(TDAEFireDACConnection($43CAA04) as IDAConnection,TDADelta($377D59C) as IDADelta,[ctDelete],$5166BA0)
DataAbstractService_Impl.TDataAbstractService.BP_ProcessChanges($5166BA0,[ctDelete],False)

ContactsServer.TCompanyServerRules.BeforeProcessChange($36E67D0,ctUpdate,$37AACE8,True)
uDABusinessProcessor.TDABusinessProcessor.DoBeforeProcessChangeEvent($37AACE8,True,True)
uDABusinessProcessor.TDABusinessProcessor.ProcessDelta(TDAEFireDACConnection($43CAA04) as IDAConnection,TDADelta($377D59C) as IDADelta,[ctInsert,ctUpdate],$5166BA0)
DataAbstractService_Impl.TDataAbstractService.BP_ProcessChanges($5166BA0,[ctInsert,ctUpdate],True)

The first call is obviously for the Deletes and the second for the Inserts and Updates. What I don’t understand is why my method is being called twice. Surely, as the change in question is of type ctUpdate, it should only call the method in the second case, when processing Inserts and Updates?

In the first case, the ProcessChange parameter is set to False rather than True. I was under the impression that this (var) parameter was there for me to use to prevent the change from being applied should I so wish. Should I actually be checking the value of this parameter to see if I should be doing anything in my method? This doesn’t seem to be documented anywhere that I can see.

In uDADataTable, change TDADataTable.InternalOnAfterApplyUpdates to be as follows:

procedure TDADataTable.InternalOnAfterApplyUpdates;
begin
  if CanProcessUserEvents and Assigned(fOnAfterApplyUpdates) then fOnAfterApplyUpdates(Self);
  if Assigned(fBusinessRules) then fBusinessRules.OnAfterApplyUpdates(Self);
end;

You’ll see the code error when you look :wink: (it’s already reported and fixed for next update)

I’ve modified the code but I’m still getting the original behaviour.

Looking at TDABusinessProcessor.ProcessDelta, I’m seeing this code:

      ok := change.ChangeType in ChangeTypes; // Filters
      canremove := (not ((lAutoIncFld <> -1) and (change.ChangeType  = ctInsert))) and not change.RefreshedByServer;
      // Even if there might not be a command associated, the user might want to do something with this
      // We just give an override chance here
      DoBeforeProcessChangeEvent(change, ok, canremove);

By the looks of things this behaviour is deliberate. The “ok” variable, which is passed to the BeforeProcessChange method as the ProcessChange var parameter, is set according to whether the change type of the individual delta change is in ChangeTypes, as I surmised above. The comment seems to indicate that the handler is called regardless of this value “just in case” we need to do something.

Can you confirm that this is correct and that I should simply check the value of ProcessChange in my method and exit if False?

Ah, sorry, I was thinking it was the same thing I reported previously with the table, but it looks like it’s not, I’d wait for RO folk to have a look for you.

it allows/disallows to process given change according to your UpdateRules.

          ok := change.ChangeType in ChangeTypes; // Filters
          ...
          // Even if there might not be a command associated, the user might want to do something with this
          // We just give an override chance here
          DoBeforeProcessChangeEvent(change, ok, canremove);

You can change default behavior in some complicated business conditions, say, disallow specific change.

If you have no such complicated conditions, this code can be used inside TCompanyServerRules.BeforeProcessChange:

  if not ProcessChange then exit;
  // existing code