1

Is it always necessary to write a trigger which can be run only once?

And what is the best way to control a trigger recursion.

I have tried the below code to handle trigger recursion;

Trigger


trigger TestTrigger on Campsite__c (before insert,before update,after insert,after update) {

    if(trigger.isBefore && trigger.isInsert)
    {
        if(!TestTriggerHelper.beforeInsertOperationProcessed)
        {
            TestTriggerHelper.processBeforeInsertion(Trigger.new,Trigger.newMap);
            TestTriggerHelper.beforeInsertOperationProcessed = true;
            for(Sobject c : Trigger.new)
                system.debug(((Campsite__c)c).email__C);
        }
        else
        {

            TestTriggerHelper.beforeInsertCount++;
            system.debug('Not Processing before insert'+'  :  '+TestTriggerHelper.beforeInsertCount);
            for(Sobject c : Trigger.new)
                system.debug(((Campsite__c)c).email__c);
        }
    }

    if(trigger.isBefore && trigger.isUpdate)
    {

        if(!TestTriggerHelper.beforeUpdateOperationProcessed)
        {
            TestTriggerHelper.processBeforeUpdate(Trigger.new,Trigger.newMap);
            TestTriggerHelper.beforeUpdateOperationProcessed = true;
            for(Sobject c : Trigger.new)
                system.debug(((Campsite__c)c).email__c);
        }
        else
        {
            TestTriggerHelper.beforeUpdateCount++;
            system.debug('Not Processing before update'+'  :  '+TestTriggerHelper.beforeUpdateCount);
            for(Sobject c : Trigger.new)
                system.debug(((Campsite__c)c).email__c);
        }

    }

    if(trigger.isAfter && trigger.isInsert){

        if(!TestTriggerHelper.afterInsertOperationProcessed)
        {
            TestTriggerHelper.processAfterInsertion(Trigger.new,Trigger.newMap);
            TestTriggerHelper.afterInsertOperationProcessed = true;
            for(Sobject c : Trigger.new)
                system.debug(((Campsite__c)c).email__c);
        }
        else
        {
            TestTriggerHelper.afterInsertCount++;
            system.debug('Not Processing after insert'+'  :  '+TestTriggerHelper.afterInsertCount);
            for(Sobject c : Trigger.new)
                system.debug(((Campsite__c)c).email__c);
        }
    }

    if(trigger.isAfter && trigger.isUpdate)
    {

        if(!TestTriggerHelper.afterUpdateOperationProcessed)
        {
            TestTriggerHelper.processAfterUpdate(Trigger.new,Trigger.newMap);
            TestTriggerHelper.afterUpdateOperationProcessed = true;
            for(Sobject c : Trigger.new)
                system.debug(((Campsite__c)c).email__C);
        }
        else
        {
            TestTriggerHelper.afterUpdateCount++;
            system.debug('Not Processing after update'+'  :  '+TestTriggerHelper.afterUpdateCount);
            for(Sobject c : Trigger.new)
                system.debug(((Campsite__c)c).email__c);
        }
    }

}

TriggerHelper


public class TestTriggerHelper {

    public static integer beforeInsertCount = 0;
    public static integer beforeUpdateCount = 0;
    public static integer afterInsertCount = 0;
    public static integer afterUpdateCount = 0;

    public static Boolean beforeInsertOperationProcessed = false;
    public static Boolean beforeUpdateOperationProcessed = false;
    public static Boolean afterInsertOperationProcessed = false;
    public static Boolean afterUpdateOperationProcessed = false;

    public static void processBeforeInsertion(List newOrders,Map newOrdersMap)
    {
        beforeInsertCount++;
        system.debug('Processing before insert'+'  :  '+beforeInsertCount);
    }    


    public static void processBeforeUpdate(List newOrders,Map newOrdersMap)
    {
        beforeUpdateCount++;
        system.debug('Processing before update'+'  :  '+beforeUpdateCount);
    }    

     public static void processAfterInsertion(List newOrders,Map newOrdersMap)
    {
        afterInsertCount++;
        system.debug('Processing after insert'+'  :  '+afterInsertCount);
    }    


    public static void processAfterUpdate(List newOrders,Map newOrdersMap)
    {
        afterUpdateCount++;
        system.debug('Processing after update'+'  :  '+afterUpdateCount);
    }    

}

I have a Campsite object, and have two workflow rules with field updates which make the trigger get fired totally 3 times (Once for Insert or update and two times because of workflow updates).

workflow rule ‘Rule - 1’ with condition Name contains ‘Test’ i.e.
“Campsite: Campsite NameCONTAINSTest”
Action is a field update which updates Name to ‘Best’.

workflow rule ‘Rule - 2’ with condition Name contains ‘Best’ i.e.
“Campsite: Campsite NameCONTAINSBest”
Action is a field update which updates Name to ‘Rest’.

Though it works for various scenarios as inserting more that 200 records or updating more than 200 records, but it fails for upsert.

TestScript:

campsite__c c = [select ID,Name,email__c from campsite__c limit 1];
c.name='Test';

campsite__c c2 = new Campsite__C(NAme='Test',email__c='9998@test.com');
upsert new List{c,c2};

I can see in debug log that object c is not processed. Please do enlighten me the best approach.

  • check this post best way to handler trigger Recursive call – Ratan Paul Apr 20 '16 at 11:10
  • @sfdcweb I believe your scenario is little different. As you are trying to insert more that 200 from anonymous block you are running into that scenario. But in my case I am trying only for 2 records and as it is an upsert I am running in to prob. – Saumya Ranjan Satapathy Apr 20 '16 at 11:19
  • Have you created a test class for this? I suspect there are two issues you're encountering. One is that you're not using lists for your static variables for this type of control. The other is that you're not tracking Ids in BeforeUpdate or the AfterUpdate. Otherwise, you might be best using a plain static boolean without the counting that's a pure on-off switch. – crmprogdev Apr 20 '16 at 14:53
  • @crmprogdev I did not get your first point. Can you throw me a sample code for that. And I think keeping a static set<Id> i.e. the second point will actually solve my problem. Sometime back I was just thinking what would I keep in case of before insert, where as the truth is before/after insert will never run in to recursion. It is only the update that we need to take care. Thanks. – Saumya Ranjan Satapathy Apr 20 '16 at 17:00
  • Depending on what your code does, you may also want to prevent your inserted records (either before Insert or after Insert) from being updated. Since I don't know what type of recursion you're trying to prevent, it's impossible for me to fully advise you. Instead, I can only show you some of the various techniques and strategies that I'm aware of. – crmprogdev Apr 20 '16 at 17:29
0

In your current incarnation of tracking recursion, your helper class is only using an integer to hold a "count", not a list of Ids or a string containing a unique field identifier that you can track to identify a record for re-entry. Using a static list of type Id or string, you could do that.

public class TestTriggerHelper {

    public static boolean reEntry = false;
    public static boolean B4UpdateReEntry = false;
    public static list<string> beforeUpdateIds;
    public static list<string> afterInsertIds;
    public static list<string> afterUpdateIds;


    public static void processBeforeUpdate(List newOrders,)
    {
        for(sObject n:newOrders){
           beforeUpdateIds.add(n.Id);
        system.debug('Processing before insert'+'  :  '+ n.Id+ '  '+ beforeUpdateCount);
        B4UpdateReEntry = true;

    }

    ............ etc

}    

The above would give you a list of all Ids that had just been processed in a beforeUpdate trigger. It would also set a boolean to true to let you know that you'd entered that part of your trigger which you could use to test for recursion.

What I'd expect you to be most concerned about though is recursion related to afterInsert and AfterUpdate triggers since they will also fire and your workflow can cause you to go back through them another time (once for each trigger that gets fired).

The real question becomes, what do I want to allow to fire the 2nd time around?

Right now, your trigger is organized very specifically along the lines of:

if(trigger.isBefore && trigger.isInsert)

If you were instead to rearrange it to such that it went more like:

if(trigger.isBefore){

    if(!TestTriggerHelper.reEntryB){

        TestTriggerHelper.reEntryB = true;

        if(trigger.isInsert){}

        if(trigger.isUpdate){}

    }

}

if(trigger.isAfter){

    if(!TestTriggerHelper.reEntryA){

        TestTriggerHelper.reEntryA = true;

        if(trigger.isInsert){}

        if(trigger.isUpdate){}

    }
}

Doing what's above would prevent records that entered the system via an upsert (both after insert and after update from coming back through your trigger during the same execution context. Note that I've used two different boolean variables here to distinguish between the Before and the After parts of your trigger. This is the simplest way to handle your issue.

If you want to go to the trouble of handling this via lists, you can do that, but you'll also need to find a unique field, or combination of fields you can concatenate, to identify records as being unique to prevent recursion between the before Insert and Before Update parts of the trigger.

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.