function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
ChickenOrBeefChickenOrBeef 

Recursion check prevents After triggers from running

Greetings everyone,

I recently changed all my triggers to classes and then created a main trigger for each object that calls the classes for each respective object.

Anywho, my opportunity trigger calls quite a few classes. The issue is that if I don't add a recursion check, I can't deploy the trigger due to the SOQL query limit being broken. But if I do add the recursion check, only the Before triggers work. Not the After triggers.


Here is the trigger (with the recursion call up top):

trigger MainTriggerOpportunity on Opportunity (before insert, before update, after insert, after update) {
   
    if(checkRecursive.runOnce()){
   
    if(trigger.isBefore){
        if(trigger.isInsert){
            ClassOppIndustry updater = new ClassOppIndustry();
            updater.updateOppIndustry(trigger.new);
           
            ClassRenewalDate updater1 = new ClassRenewalDate();
            updater1.updateRenewalDate(trigger.new);
        }
        if(trigger.isUpdate){
            ClassOppIndustry updater = new ClassOppIndustry();
            updater.updateOppIndustry(trigger.new);
           
            ClassRenewalDate updater1 = new ClassRenewalDate();
            updater1.updateRenewalDate(trigger.new);
        }
    }
   
    if(trigger.isAfter){
        if(trigger.isInsert){
            ClassRenewalProcess updater = new ClassRenewalProcess();
            updater.updateRenewalStatus(Trigger.new);
           
            ClassOppBrandCreate updater1 = new ClassOppBrandCreate();
            updater1.addBrand(trigger.new);
        }
        if(trigger.isUpdate){
            ClassRenewalProcess updater = new ClassRenewalProcess();
            updater.updateRenewalStatus(Trigger.new);
           
            ClassOppBrandCreate updater1 = new ClassOppBrandCreate();
            updater1.addBrand(trigger.new);
           
            ClassChatterAlerts updater2 = new ClassChatterAlerts();
            updater2.addChatterAlert(Trigger.new,Trigger.oldMap);
        }
    }
}
   
}




Here is the recursion check class:

public Class checkRecursive{
   
    private static boolean run = true;
   
    public static boolean runOnce(){
       
    if(run){
     run=false;
     return true;  
    }
    else{
        return run;
    }
       
    }
}



Perhaps I have to allow the trigger to run twice? I'm a newb, so any help would be much appreciated!

Thanks,
Greg
Blake TanonBlake Tanon
Since it's the same transaction (before and after) the recursive is already marked true.  You'd have to put it inside of the before and the after conditions, but if you're hitting SOQL limits I don't think that would help you either way.  What are you doing that hits these limits?
Chidambar ReddyChidambar Reddy
Hi,

Try the following changes.

trigger MainTriggerOpportunity on Opportunity (before insert, before update, after insert, after update) {
  
  //  if(checkRecursive.runOnce()){
  
    if(trigger.isBefore){
      if(checkRecursive.runBeforeOnce()){
        if(trigger.isInsert){
            ClassOppIndustry updater = new ClassOppIndustry();
            updater.updateOppIndustry(trigger.new);
          
            ClassRenewalDate updater1 = new ClassRenewalDate();
            updater1.updateRenewalDate(trigger.new);
        }
        if(trigger.isUpdate){
            ClassOppIndustry updater = new ClassOppIndustry();
            updater.updateOppIndustry(trigger.new);
          
            ClassRenewalDate updater1 = new ClassRenewalDate();
            updater1.updateRenewalDate(trigger.new);
        }
      }// End of IF added
    }
  
    if(trigger.isAfter){
      if(checkRecursive.runAfterOnce()){
        if(trigger.isInsert){
            ClassRenewalProcess updater = new ClassRenewalProcess();
            updater.updateRenewalStatus(Trigger.new);
          
            ClassOppBrandCreate updater1 = new ClassOppBrandCreate();
            updater1.addBrand(trigger.new);
        }
        if(trigger.isUpdate){
            ClassRenewalProcess updater = new ClassRenewalProcess();
            updater.updateRenewalStatus(Trigger.new);
          
            ClassOppBrandCreate updater1 = new ClassOppBrandCreate();
            updater1.addBrand(trigger.new);
          
            ClassChatterAlerts updater2 = new ClassChatterAlerts();
            updater2.addChatterAlert(Trigger.new,Trigger.oldMap);
        }
      }// End of IF added
    }

  
}



Here is the recursion check class:

public Class checkRecursive{
  
    private static boolean runbefore = true;
    private static boolean runafter = true;
  
    public static boolean runBeforeOnce(){
      
    if(runbefore){
     runbefore=false;
     return true; 
    }
    else{
        return runbefore;
    }
      
    }

    
    public static boolean runAfterOnce(){
     
    if(runafter){
     runafter=false;
     return true;
    }
    else{
        return runafter;
    }
     
    }


}


-----
Thank you 
Choose it as Best Answer if it resolved your issue.
ChickenOrBeefChickenOrBeef
@Blake

I have a BEFORE trigger on Opportunities that contains a SOQL query, and I also have an AFTER trigger on Opportunities that updates another Opportunitiy. I believe that causes the recursion. I actually made a thread on here before about those two triggers, and then someone pointed me towards the recursion check class I used.

Here is the thread: https://developer.salesforce.com/forums/ForumsMain?id=906F00000009HzbIAE


@Chidambar

I made the changes and the trigger seems to work, but now I'm trying to get 100% code coverage on the main trigger. I can only get up to 61% now, while I was able to get 100% without the new recursion check you made.

I'm not sure how testing works once you only use one main trigger that references classes. Do I just need to make a test class that inserts and updates an opportunity? Or do I need to test the code in all the classes as well? Does this recursion check change things?

Thanks for the help so far, but any further guidance would be much appreciated!

Thanks,
Greg
ChickenOrBeefChickenOrBeef
Also, here's the test class I'm using, which should cause all the individual classes to run. I'm only getting 61% coverage:

@isTest
public class testChatterAlerts{
    static testMethod void testChatter() {

    Account tAccount = new Account();
      tAccount.Name = 'Coca Cola';
      tAccount.Region__c = 'Canada';
      tAccount.Industry = 'Sports';
      tAccount.Status__c = 'Prospect';
      tAccount.Website = 'www.cocacola.com';
      tAccount.FB_Page_1_Fans__c = 30000;
      tAccount.FB_Page_1_Link__c = 'www.facebook.com/cocacola';
      tAccount.BillingState = 'NY';
      tAccount.Most_Recent_Subscription__c = 'Annual';
      INSERT tAccount;
       
      Account tBrand = new Account();
      tBrand.Name = 'Cherry Coke';
      tBrand.Region__c = 'APAC';
      tBrand.Industry = 'Restaurants';
      tBrand.Status__c = 'Prospect';
      tBrand.Website = 'www.cherrycoke.com';
      tBrand.FB_Page_1_Fans__c = 10500;
      tBrand.FB_Page_1_Link__c = 'www.facebook.com/cherrycoke';
      tBrand.BillingState = 'CT';
      tBrand.Most_Recent_Subscription__c = 'Fixed Term';
      INSERT tBrand;

      Contact tContact = new Contact();
      tContact.FirstName = 'Bob';
      tContact.LastName = 'Barker';
      tContact.AccountId = tAccount.Id;
      INSERT tContact;

      Opportunity tOppty = new Opportunity();
      tOppty.Name = 'Coka Annual';
      tOppty.StageName = 'Active Discussions';
      tOppty.AccountId = tAccount.Id;
      tOppty.Brand__c = tBrand.Id;
      tOppty.Lead_Origin__c = 'Inbound Lead';
      tOppty.Primary_Contact__c = tContact.Id;
      tOppty.CloseDate = date.parse('3/15/14');
      tOppty.Effective_Date__c = date.parse('3/18/14');
      tOppty.Term__c = 5;
      INSERT tOppty;
       
      Opportunity rOppty = new Opportunity();
      rOppty.Name = 'Coka Renewal';
      rOppty.StageName = 'Call Held';
      rOppty.AccountId = tAccount.Id;
      rOppty.Renewed_Opportunity__c = tOppty.Id;
      rOppty.Lead_Origin__c = 'Inbound Lead';
      rOppty.Primary_Contact__c = tContact.Id;
      rOppty.CloseDate = date.parse('9/15/14');
      INSERT rOppty;

      rOppty.StageName = 'Negotiations';
      rOppty.Brand__c = tBrand.Id;
      rOppty.Effective_Date__c = date.parse('10/18/14');
      rOppty.Term__c = 7;
      UPDATE rOppty;
       
  }
}
jjvdevjjvdev
Nithya S 1Nithya S 1
Hi Greg,
I know this an old post, but what was the solution for this?

Thanks!
ChickenOrBeefChickenOrBeef
Hey Nithya,

In my original post I had one recursion check for the entire trigger. The solution was to have a separate recursion check for each instance of Before/Insert, Before/Update, After/Insert, After/Update, etc.

For example, my Account trigger looks like this:
 
trigger MainTriggerAccount on Account (before insert, before update, after insert, after update) {
    
    IF(trigger.isBefore){
        
        IF(trigger.isInsert){
            
            if(checkAccountRecursive.runBeforeInsertOnce()){
            
                //Call classes
                
            }
        }
        
        IF(trigger.isUpdate){
            
            if(checkAccountRecursive.runBeforeUpdateOnce()){
                
                //Call classes
                
            }
        }

    }
        
    if(trigger.isAfter){
        
        if(trigger.isInsert){
            
            if(checkAccountRecursive.runAfterInsertOnce()){
                
                //Call classes
                
            }
            
        }
    
        if(trigger.isUpdate){
            
            if(checkAccountRecursive.runAfterUpdateOnce()){
                
               //Call classes
                
            }
                
        }

    }
    
}

 And my recursion check class for the Account trigger looks like this:
 
public class checkAccountRecursive{
    
    private static boolean runBeforeInsert = true;
    private static boolean runBeforeUpdate = true;
    private static boolean runAfterInsert = true;
    private static boolean runAfterUpdate = true;
    
    public static boolean runBeforeInsertOnce(){
        
    if(runBeforeInsert){
     runBeforeInsert = false;
     return true;   
    }
    else{
        return runBeforeInsert;
    }
        
    }
    
    public static boolean runBeforeUpdateOnce(){
        
    if(runBeforeUpdate){
     runBeforeUpdate = false;
     return true;   
    }
    else{
        return runBeforeUpdate;
    }
        
    }
    
    public static boolean runAfterInsertOnce(){
        
    if(runAfterInsert){
     runAfterInsert = false;
     return true;   
    }
    else{
        return runAfterInsert;
    }
        
    }
    
    public static boolean runAfterUpdateOnce(){
        
    if(runAfterUpdate){
     runAfterUpdate = false;
     return true;   
    }
    else{
        return runAfterUpdate;
    }
        
    }
    
}

-Greg
Nithya S 1Nithya S 1
Thanks Greg!
Nithya S 1Nithya S 1
Hi Greg, Here are the trigger conditions. I added the recursion check for the following. But it still seems to fail. These variables are declared false in the recursion check class. if(trigger.Isupdate && trigger.IsAfter){ } if(trigger.isinsert || (trigger.isupdate && trigger.isbefore)){ if(!Acc_RecursionCheck.RecursionInsert) { Acc_RecursionCheck.RecursionInsert = true; .... .... } if(trigger.isUpdate){ if(!Acc_RecursionCheck.RecursionUpdate ) { Acc_RecursionCheck.RecursionUpdate = true; ... ... } if(trigger.isBefore){ if(!Acc_RecursionCheck.RecursionBefore ) { Acc_RecursionCheck.RecursionBefore = true; ... .. } Do I have to combine the variables as well? Thanks!
ChickenOrBeefChickenOrBeef
It looks like you have a recursion check for Insert, one for Update, one for Before, and one for After, but instead you need a recursion check for each combination of those. So for example, you would need a BeforeInsert check, a BeforeUpdate check, an AfterInsert check, etc.
David Kingery 6David Kingery 6
Correct me if I'm wrong, but shouldn't you only need to check IsBefore and IsAfter?  I guess Upserts might hit both Insert and Update, but otherwise there should never be conflicts between them, right?
SForceBeWithYouSForceBeWithYou

So, my biggest problem with this pattern is.... There's a single class for a recursion check??? You've got to be kidding me if you're going to A) Call a class "recursionCheck" (and I'm not just talking about lowerCamelCasing it), but B) think that you're only going to have a single occurrence of this in your entire org.

Optimally, you would want to have one or more Boolean static variables on your trigger handlers for each use case.

Example:

public with sharing AccountTriggerHandler {
    public static Boolean hasValidatedBillingAddress = false;
    public static Boolean hasDoneSomeOtherThing = false;


    public static void validateBillingAddresses(List<Account> accts) {
        if (!hasValidatedBillingAddress) {
            hasValidatedBillingAddress = true;
            // do stuff
        }
    }
}

public with sharing CaseTriggerHandler {
    public static Boolean hasUpdatedCaseStatus = false;
    
    public static void updateCaseStatus(List<Case> cases) {
        // you get the idea
    }
}

Remember, making something static only persists for the execution context, which is almost always what you're looking to keep track of in a recursive trigger scenario.  Please, no more checkRecursive!!