Monday, November 21, 2011

Salesforce and Max Trigger Depth

So, I regularly keep an eye on the apex/VF message boards for salesforce and there are a couple of recurring issues that arise over and over again. I have already written on the issue of test classes here, but another one that pops up at least once a week if not more is the issue of Max Trigger Depth.

What Happens

Simply put when you are writing a trigger and start to insert items you get an error similar to this:

System.DmlException: Insert failed. First exception on row 0; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, XXX_OBj: maximum trigger depth exceeded XXX_Trigger trigger event BeforeInsert

Why this Happens
Generally, when you receive this error you have updated the same object that your Trigger is working on with a DML statement in your trigger. Here is an example:

Trigger someTrigger on some_obj__c (before insert, before update){
  for (some_Obj__c o: trigger.new){
    o.somefield__c = 'This is my data';
    update o;
  }
}

Now there are several things wrong with that trigger, but for our purposes the one that matters is the I am updating some_Obj__c in a trigger on that same object that activates on update. So what happens is that the trigger is called, the object is updated, the trigger is called again and the object is updated again causing the trigger to be called again and so on. After a certain point there are too many triggers being called and you receive the error message above. Be warned that sometimes finding that DML statement, may not be as easy as it sounds. It could be in some utility class that is being called by a helper class that is called by your trigger and you may not even realize it!

How to solve the problem

Solving this problem my not be as easy as finding this problem. Obviously, the easiest way to resolve this issue would be to remove the offending DML statement, but this may not be possible so we have to investigate other workarounds.
At the most basic level what you have to do is add some sort of check that will stop the trigger loop, preferably before you get deep enough to throw an error. This can be accomplished in many ways. I will quickly outline a couple, but this is by no means an exhaustive list.

1. Check a field on the object. Basically, create a scenario where the object has a field that will somehow indicate that the record has already been updated. Then all you do is check this field, if it indicates that the record has already been updated, then you do not call the DML statement, the chain is broken and everything will terminate normally.

2. Use a static variable. This is similar to number one, but instead of using a field on the object, you use a static data member of a public class to check and see if the DML statement needs to be called. This is effective if there is no field on the object to indicate if the update needs to be performed, or if the update is on a different record than the one that fired the trigger.

There are probably other ways to stop the execution loop, but the general idea remains the same.

No comments: