10

So there are tons of questions on here an d on the boards around using static variables to keep trigger from recursively calling itself. There also is a lot of information out there around best practices in triggers. These bets practices include only having one trigger per object and executing all logic outside of the trigger itself in a helper class. My issue seems to be trying to merge these two best practices. Helper classes for triggers, and use of static variables to prevent recursive trigger calls

Just for simplicity sake, lets assume the logic in the helper classes is ok. It's bulkified, its optimized, etc. Here is my Account Trigger

trigger AccountTrigger on Account (before insert, after insert, before update, after update) {

    AccountTriggerHelper helper = new AccountTriggerHelper();

    if(trigger.isBefore && trigger.isUpdate){
        helper.updateAccountUpdateSummary(trigger.new, trigger.old, trigger.newMap, trigger.oldMap);    
    }   

    if(trigger.isAfter){
        if(trigger.isUpdate){
            helper.maintainCAAccountTeamMembers(trigger.new, trigger.old, trigger.newMap, trigger.oldMap, trigger.isInsert, trigger.isUpdate);
            helper.updateCortextOpportunityOwners(trigger.new, trigger.old, trigger.newMap, trigger.oldMap);
        }
        if(trigger.isInsert){
            helper.maintainCAAccountTeamMembers(trigger.new, trigger.old, trigger.newMap, trigger.oldMap, trigger.isInsert, trigger.isUpdate);
        }
    }
}

As you can see I just have a helper class that I call the methods to execute the logic for the trigger. These all work great if I have them separated (each their own trigger). v The issue is I have test code that when I run is erroring out with a 'Too many SOQL queries' error.

When I check the debug logs, it seems that my triggers are being called recuresively, and it keeps going until it has run enough times in the same context to get the too many SOQL queries error.

So I thought I would use my TriggerContextUtility class to help with this. Its just a class with a static variable to keep track of the first run of the trigger. This works fine in my triggers with only one method being called.

Heres the triggerContetUtility class

public class TriggerContextUtility {

    private static boolean firstRun = true;

    public static boolean isFirstRun() {
        return firstRun;
    }
    public static void setFirstRunFalse(){
        firstRun = false;
    }
}

Very simple. Normally, I would use this utility class like below to ensure that the trigger only fires once per context, but adding it like below doesn't work

trigger AccountTrigger on Account (before insert, after insert, before update, after update) {

    AccountTriggerHelper helper = new AccountTriggerHelper();
    if(TriggerContextUtility.isFirstRun()){
        if(trigger.isBefore && trigger.isUpdate){
            helper.updateAccountUpdateSummary(trigger.new, trigger.old, trigger.newMap, trigger.oldMap);    
        }   

        if(trigger.isAfter){
            if(trigger.isUpdate){
                helper.maintainCAAccountTeamMembers(trigger.new, trigger.old, trigger.newMap, trigger.oldMap, trigger.isInsert, trigger.isUpdate);
                helper.updateCortextOpportunityOwners(trigger.new, trigger.old, trigger.newMap, trigger.oldMap);
            }
            if(trigger.isInsert){
                helper.maintainCAAccountTeamMembers(trigger.new, trigger.old, trigger.newMap, trigger.oldMap, trigger.isInsert, trigger.isUpdate);
            }
        }
    }
    TriggerContextUtility.setFirstRunFalse();
}

This does not work however. It seems my before methods are called, but not my after methods. I have tried moving the TriggerContextUtility.setFirstRunFalse(); into different places, but nothing seems to correctly fire all methods.

So I either leave out my static variable check and the trigger is recursively calling itself until it errors out. OR, I fix this recursive call using my TriggerContextUtility class, but then not all methods within the trigger are being called.

Can anyone offer any insight as to what I am doing wrong here? Am I missing something. This seems like something that should be simple, but for some reason I can't get it to work. Hoping for another set of eyes to find the flaw in my logic here.

EDIT

Here is the debug statements I added to the trigger

trigger AccountUpdatesTrigger on Account (before insert, after insert, before update, after update) {

    AccountTriggerHelper helper = new AccountTriggerHelper();
    if(TriggerContextUtility.isFirstRun()){
        if(trigger.isBefore && trigger.isUpdate){
            helper.updateAccountUpdateSummary(trigger.new, trigger.old, trigger.newMap, trigger.oldMap);    
        }   
        *system.debug('First Run Value Line 8: ' + TriggerContextUtility.isFirstRun());*

        if(trigger.isAfter){
            if(trigger.isUpdate){
                *system.debug('First Run Value Line 11: ' + TriggerContextUtility.isFirstRun());*
                helper.maintainCAAccountTeamMembers(trigger.new, trigger.old, trigger.newMap, trigger.oldMap, trigger.isInsert, trigger.isUpdate);
                helper.updateCortextOpportunityOwners(trigger.new, trigger.old, trigger.newMap, trigger.oldMap);
                *system.debug('First Run Value Line 14: ' + TriggerContextUtility.isFirstRun());*
            }
            if(trigger.isInsert){
                *system.debug('First Run Value Line 17: ' + TriggerContextUtility.isFirstRun());*

                helper.maintainCAAccountTeamMembers(trigger.new, trigger.old, trigger.newMap, trigger.oldMap, trigger.isInsert, trigger.isUpdate);
            }
            TriggerContextUtility.setFirstRunFalse();
        }
    }   
}

The line 14 debug statement is outputting FALSE. Why would that be?

  • What happens if you remove the isBefore block? Does the isAfter block get called as expected? – Mike Chale Oct 30 '13 at 19:39
  • If I remove the before block the only method that runs is the method within that block. None of the After methods are called – Chris Duncombe Oct 30 '13 at 19:54
  • Why is the recursion happening? It seems like your problem is in your helper code, which we can't see. As a general rule of thumb, you shouldn't be performing a DML on the object you're on in your after trigger. This can create recursion. That logic should be in the before trigger. – greenstork Oct 30 '13 at 20:35
  • I find setting static variables immediately before a DML is the only way to have precise control. With that then, I recommend moving the setting of your static variable to you helper class. – greenstork Oct 30 '13 at 20:36
  • So would I set it inside each individual method within the helper class, as @britishBoyinDC suggested? – Chris Duncombe Oct 30 '13 at 20:38
7

I don't think you want the ContentUtility check to be at the Trigger level - I typically refer to it in my helper class, or even at the individual method level in that class. There may well be instances where you want one method to run twice if something was updated, but not others. So I would move the check for TriggerContextUtility into the helper class and I think it will work fine...

As an update - I have always used these posts as my guide.

http://gokubi.com/archives/two-interesting-ways-to-architect-apex-triggers http://www.embracingthecloud.com/2010/07/08/ASimpleTriggerTemplateForSalesforce.aspx

There are (many!) more variations, but I would keep simple at this stage... – BritishBoyinDC 1 min ago edit

  • hmmmm....sO i would add a check for first run in each method in the helper class and then set it to false at the end of each method. Isnt each method call in the same context though? So wont it be set to false after I call the first method, so then when I call the second method in the helper class, wont it still be false and therefore skip that method? – Chris Duncombe Oct 30 '13 at 19:34
  • I would add the check at the method level where appropriate - but I would typically toggle the flag at the end of the logic for the after update process - it is the update process you want to avoid happening recursively... – BritishBoyinDC Oct 30 '13 at 20:38
  • To expand on that, I would change your structure to call methods in the helper class that correspond to the events in the trigger e.g. helper.beforeinsert, helper.afterupdate - and then within those, callout to the individual pieces of logic. That way, the final line of the helper.afterupdate method sets the firstrun to false. – BritishBoyinDC Oct 30 '13 at 21:14
  • Ok, so really, then I could have a construct to follow where each trigger has a helperclass with exactly 7 methods, one for each DML option. Rather than having methods for function, like i Have now, where an afterupdate for example could possible call 3 methods? – Chris Duncombe Oct 30 '13 at 21:18
  • This is how I've always controlled recursion, good answer. – greenstork Oct 30 '13 at 21:35
1

Did you try the following version?

trigger AccountTrigger on Account (before insert, after insert, before update, after update) {

    AccountTriggerHelper helper = new AccountTriggerHelper();
    if(TriggerContextUtility.isFirstRun()){
        if(trigger.isBefore && trigger.isUpdate){
            helper.updateAccountUpdateSummary(trigger.new, trigger.old, trigger.newMap, trigger.oldMap);    
        }   

        if(trigger.isAfter){
            if(trigger.isUpdate){
                helper.maintainCAAccountTeamMembers(trigger.new, trigger.old, trigger.newMap, trigger.oldMap, trigger.isInsert, trigger.isUpdate);
                helper.updateCortextOpportunityOwners(trigger.new, trigger.old, trigger.newMap, trigger.oldMap);
            }
            if(trigger.isInsert){
                helper.maintainCAAccountTeamMembers(trigger.new, trigger.old, trigger.newMap, trigger.oldMap, trigger.isInsert, trigger.isUpdate);
            }

            TriggerContextUtility.setFirstRunFalse();
         }

     }

}

I would expect this to work because:

  1. Your before update trigger fires, static variable is true so the first if is true and the before update is executed.
  2. Context variable is still true
  3. After trigger fires, still the first if (TriggerContextUtility.setFirstRunFalse()) is true so trigger.isAfter part is executed and at the end the static variable is set to false.
  4. I guess you have workflows updating thus the trigger is invoked once again but this time the first if should be false... the end

I also prefer to have the trigger stoppers in the trigger because it makes it more readable... It's also part of the trigger logic and not the Helper Class logic right ?

  • I have tried that. If I do that none of the after trigger methods get called. – Chris Duncombe Oct 30 '13 at 19:44
  • really ? :( can you add debug statements at the top to report the static variable value ? – ManSpan Oct 30 '13 at 19:59
  • HMMMM.....I added debug statements and found something. I updated the question with the debug results. What do you think? – Chris Duncombe Oct 30 '13 at 20:18
  • can it be that any of the two methods in between also sets the static variable ? or maybe they are re triggering Account triggers – ManSpan Oct 30 '13 at 20:22
  • No, neither of them make any mention of the static variable – Chris Duncombe Oct 30 '13 at 20:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.