Workflow rule causing trigger to fire twice

I have a trigger that creates new child records when a field's value is changed and ended up with a similar situation where I ended up with two child records per field change when workflow was run. In the end I created a Map to record what records were process (by Id, key of the map) and the new value of the field when I created the child record. I then checked this map to make sure I wasn't re-processing the same field update.

It's a lot of extra complexity and code and my use case is simpler than a lot. I'm not quite sure why salesforce things running triggers twice for the same transaction when workflow is involved is a good thing.


The example in the cookbook should work regardless of bulk size. Create a helper class, here's what I typically use:

global class SingleExecution {

private static boolean blnAlreadyDone = false;

public static boolean hasAlreadyDone(){ 
    return blnAlreadyDone;
}

public static void setAlreadyDone() {
    blnAlreadyDone = true;  
}

public static void forceResetAlreadyDone() {
    blnAlreadyDone = false; 
}

static testMethod void testSingleExecution() {
    //Hasn't already run
    System.assertEquals(false,SingleExecution.hasAlreadyDone());

    SingleExecution.setAlreadyDone();
    //Has just been run
    System.assertEquals(true,SingleExecution.hasAlreadyDone());

    SingleExecution.forceResetAlreadyDone();
    //Has just been reset
    System.assertEquals(false,SingleExecution.hasAlreadyDone());
}   
}

then in your trigger you can do

trigger AccountTrigger on Account (before update) {
    if(SingleExecution.hasAlreadyDone()) return;
    //Else
    SingleExecution.setAlreadyDone();
    //Do your stuff
}

Even though you may data load say 350 accounts which would cause this trigger to fire twice...once for the fist 200 records and then again for the next 150 records each time the trigger fires it has it's own context and so the SingleExecution flag is re-set in each of those separate contexts.

You can also get more fancy with your SingleExecution and allow it to be used across various triggers/classes say for preventing @future from being called more than once because instead of a single boolean, it lets you pass a string that with the name of your class. Because of context you typically don't need this, however if you had two triggers that might call each other and you want them BOTH to fire once, then you'd need to differentiate between them somehow.

Hope this helps...

global without sharing class SingleExecution {

private static Map<String,Boolean> singletonMap;

global static Boolean hasAlreadyExecuted(String ClassNameOrExecutionName){  
    if(singletonMap != null) {
        Boolean alreadyExecuted = singletonMap.get(ClassNameOrExecutionName);
        if(alreadyExecuted != null) {
            return alreadyExecuted;
        }           
    }
    //By default return false
    return false;
}

global static void setAlreadyExecuted(String ClassNameOrExecutionName) {
    if(singletonMap == null) {
        singletonMap = new Map<String,Boolean>();
    }
    singletonMap.put(ClassNameOrExecutionName,true);
}

static testMethod void testSingleExecution() {
    //Hasn't already run
    System.assertEquals(false,SingleExecution.hasAlreadyExecuted('testSingleExecution'));

    SingleExecution.setAlreadyExecuted('testSingleExecution');
    //Has just been run
    System.assertEquals(true,SingleExecution.hasAlreadyExecuted('testSingleExecution'));
}

}

While I saw the solution described (store IDs in a map or set), I didn't see any code. In the event you want something that you can cut and paste. Disclaimer: I have tested this against single record updates and it works fine (prevents my logic from running on the second trigger execution). I have not tested it against batch updates.

public class TriggerRunOnce {
    private static Set <Id> idSet = new Set <Id>();

    // has this Id been processed? 
    public static boolean isAlreadyDone(Id objectId) {
        return (idSet.contains(objectId));
    }

    // set that this Id has been processed.
    public static void setAlreadyDone(Id objectId) {
        idSet.add(objectId);
    }

    // empty set if we need to for some reason. 
    public static void resetAlreadyDone() {
        idSet.clear();
    }

}

and your trigger:

if (!TriggerRunOnce.isAlreadyDone(Obj.Id)) {
    // do your processing
    TriggerRunOnce.setAlreadyDone(Obj.Id);
}