18

I understand from experience that Master-Detail deletion triggers do not cascade. For example if I delete my Account the Contact deletion triggers don't fire.

Having devoured the first and second editions of the outstanding book Advanced Apex by Dan Appleman, there's one point in the chapter on triggers I'm looking to clarify:

Delete triggers typically do not fire on cascade deletes. If you need to detect deletion of the child objects, you must create a before-delete trigger on the parent object and perform your desired operation on the child objects at that time (be sure to design this carefully - there may be a large number of child objects).

So if I had a trigger like this on Contact:

trigger ContactBeforeDelete on Contact (before delete) {
    ContactNotifier.alertDeletedContacts(Trigger.old);
}

The book encourages creating one likewise on Account:

trigger AccountBeforeDelete on Account (before delete) {
    List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :id];
    ContactNotifier.alertDeletedContacts(contacts);
}

But to me, the obvious solution would be to cascade the deletion, not the business logic:

trigger AccountBeforeDelete on Account (before delete) {
    List<Contact> contacts = [SELECT Id FROM Contact WHERE AccountId = :id];
    delete contacts;
}

The child triggers will take care of the logic. Copying the invocation around isn't DRY. So why 'perform the desired operation on the child objects' instead of just cascading the delete? Is there some catch?

2 Answers 2

17

One of the issues with an explicitly delete of all the child records is that there may simply be too many of them. I've seen instances where a master object has more than 10,000 detail records, and if you're doing a mass delete of parents with high numbers of children that would eat through your DML rows limit very quickly.

While it's conceptually simpler to cascade the deletion I'd think carefully about expected volumes before doing so. Dan's pattern only cascades side effects of the deletion as needed.

It boils down to developer productivity vs efficient code, and every environment has different factors impacting which way that decision should go.

Edit: You'd also have to be wary of any developers taking advantage of the fact that cascade deletion doesn't invoke delete triggers and assuming that all delete operations on child records are explicit in their triggers. In a complex org that's a headache to check for.

5
  • 1
    Either method will fail with too many rows anyways; odds are, you'll run out of query row results before you run out of DML statements, since both are the same value (50,000), but the query generally has to come before the DML operation. Good point about developers intentionally hoping that the triggers are not called, although I would argue against that design on the principle that if they fix cascade deletions, they'd break those triggers (it's more of a "bug exploit"). Note also that there is a hard limit on cascade deletes anyways that's only twice the DML/query limit.
    – sfdcfox
    Commented Dec 30, 2013 at 20:14
  • 1
    Also, I don't think anyone here is advocating manually invoking deletions for no reason, so either pattern is equally viable from a technological stand-point. However, the DRY method has one advantage: if they fix cascade deletes, it will still work, while the other version might not (because the trigger's functionality will be called twice because of code duplication).
    – sfdcfox
    Commented Dec 30, 2013 at 20:19
  • 1
    @sfdcfox the total number of DML rows is currently 10k, 20% of the query row limits, large, but not as large as the query limits. I do know about the hard limit (although not the threshold), some company cough kept crashing pods by overflowing the max transaction memory in oracle. Commented Dec 30, 2013 at 21:48
  • Oh... good catch. I forgot about that bit. Still, it's hard to imagine you'd want to intentionally skip a trigger, since its usually there to protect some other data.
    – sfdcfox
    Commented Dec 30, 2013 at 22:02
  • 1
    ... I'm going to just stop now. See, I just realized that sometimes skipping the trigger would be good. In fact, I think I have a use for that right now...
    – sfdcfox
    Commented Dec 30, 2013 at 22:09
14

The statement is ambiguous when read and re-read carefully. "Desired operation" may refer to calling the business logic, or it may refer to simply calling a DML statement (as your DRY example). I personally follow the second method, because it not only simplifies your code, but also reduces the number of paths one has to scan through when a logic bug or error appears. It appears that this is more an editorial error rather than being intentionally misleading, but I would personally reach out to the author for clarification.

2
  • gotcha - by 'desired operation' Dan Appleman may mean the deletion after all Commented Dec 30, 2013 at 16:03
  • 1
    @user320 And ca_peterson made a valid point. Both approaches mean something, and both have a practical use. Sometimes you do actually need to duplicate code, if the deletion isn't significant to the processing at hand. For example, a rollup summary trigger probably doesn't need to fire if the parent record is also going to vanish, etc. So, both are correct, but conditionally so. Both are tools to be used with care.
    – sfdcfox
    Commented Dec 30, 2013 at 22:12

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .