Suppose we wanted to track a Contact’s spouse or significant other in Salesforce using a lookup field to another Contact. This type of lookup is called a “Self Relationship” in Salesforce because the Contact object is relating back to itself. We normally use self relationships to establish a sense of hierarchy, like assigning sub Accounts to parent Accounts.
But this particular self relationship does not lend itself to hierarchy. If Harry is married to Sally, then Sally is just as much married to Harry. While implementing the “Spouse Situation” in Salesforce appears to be very easy on the surface, when you actually put in into a practice, a whole slew of issues pop up that can make it very complicated very quickly.
First, let us create a trigger so that whenever Harry has Sally listed in his Spouse__c field, then Sally will automatically have Harry listed in her Spouse__c field. This seems like a fairly easy solution.
trigger MatchSpouse on Contact (after insert, after update) { // Gather spouse ids List spouseIds = new List(); for (Contact c : Trigger.new){ if (c.Spouse__c != null){ spouseIds.add (c.Spouse__c); } } // Query spouses Map<id, Contact> spouses = new Map<id, Contact>([SELECT id FROM Contact WHERE id =: spouseIds]); // Set contacts to spouses for (Contact c : Trigger.new){ if (c.Spouse__c != null && spouses.get(c.Spouse__c) != null){ spouses.get(c.Spouse__c).Spouse__c = c.id; } } update spouses.values(); }
But when we try to set the field in the Salesforce UI, we get this error:
first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, MatchSpouse: maximum trigger depth exceeded Contact trigger event AfterUpdate
Why is this happening?
First we run the trigger on Harry’s record. In line 20, we update the spouses, which will include Sally’s record because she is Harry’s spouse. When this happens, the trigger will run again on Sally, which will once again hit line 20 and then attempt to update her spouse, Harry. It is a recursive trigger, and the whole process will continue to loop until it reaches the maximum trigger depth and throws the error.
Recursive triggers pop up a lot in Salesforce development, so it is important to know how to avoid them. At Internet Creations, we use a static class called Trigger Monitor that stores a static set as strings.
public class TriggerMonitor { public static set<string> ExecutedTriggers {get;set;} static { ExecutedTriggers = new Set<String>(); } }
Because static classes can be shared across all triggers in a full execution, we can add values to this set and call back to it in a later trigger. Our MatchSpouse trigger will only have to fire off once, so if we add “MatchSpouse” to the set on the first run, we can check to see if “MatchSpouse” is in the set on the second run and return back before it can do anything.
trigger MatchSpouse on Contact (after insert, after update) { // Avoid recurive trigger If(TriggerMonitor.ExecutedTriggers.contains('MatchSpouse')) return; TriggerMonitor.ExecutedTriggers.add('MatchSpouse'); // Gather spouse ids List spouseIds = new List(); for (Contact c : Trigger.new){ if (c.Spouse__c != null){ spouseIds.add (c.Spouse__c); } } // Query spouses Map<id, Contact> spouses = new Map<id, Contact>([SELECT id FROM Contact WHERE id =: spouseIds]); // Set contacts to spouses for (Contact c : Trigger.new){ if (c.Spouse__c != null && spouses.get(c.Spouse__c) != null){ spouses.get(c.Spouse__c).Spouse__c = c.id; } } update spouses.values(); }
And now the trigger fires off with no issues. Sally’s Spouse__c field is successfully linked back to Harry when it’s set on his record.
But wait! There is still one more problem we need to take care of when it comes to the spouse situation.
With the divorce rate as high as it is these days, it is quite possible that either Harry or Sally will have to clear out their Spouse__c field later on. If this happens with our current trigger, then the partner record will never be updated. So we have to take the time to clear out the field on the spouses in our trigger.
trigger MatchSpouse on Contact (after insert, after update) { // Avoid recurive trigger If(TriggerMonitor.ExecutedTriggers.contains('MatchSpouse')) return; TriggerMonitor.ExecutedTriggers.add('MatchSpouse'); // Clear out Spouse__c on all partners Map<Id, Contact> clearedSpouses = new Map<Id, Contact>([SELECT id, Spouse__c FROM Contact WHERE Spouse__c =: Trigger.newMap.keySet()]); for (Contact c : clearedSpouses.values()) c.Spouse__c = null; // Gather spouse ids List<String> spouseIds = new List<String>(); for (Contact c : Trigger.new){ if (c.Spouse__c != null){ spouseIds.add (c.Spouse__c); } } // Query spouses Map<id, Contact> spouses = new Map<id, Contact>([SELECT id FROM Contact WHERE id =: spouseIds]); // Set contacts to spouses for (Contact c : Trigger.new){ if (c.Spouse__c != null && spouses.get(c.Spouse__c) != null){ spouses.get(c.Spouse__c).Spouse__c = c.id; clearedSpouses.remove(c.Spouse__c); } } // Update all spouses List<Contact> allContacts = new List<Contact>(); allContacts.addAll(clearedSpouses.values()); allContacts.addAll(spouses.values()); update allContacts; }
And that should solve the spouse situation.
If you have any questions about the spouse situation or recursive triggers, contact us by adding a comment below, or @ reply us on Twitter. We’re on Facebook too!