1

I have an apex class that populates a field every time that record is updated. But every time I save the record, there's an error message containing this:

maximum trigger depth exceeded

Meanwhile, here's the apex class and the apex trigger:

Apex Class:

public class DeskCX {

    public static void updateField() {

        List<Desk__c> deskList = new List<Desk__c>();

        deskList = [SELECT Id, Name, Field_1__c, Field_2__c FROM Desk__c];

        if(!deskList.isEmpty()) {


            if(deskList[0].Field_1__c != null || deskList[0].Field_2__c != null) {

                deskList[0].Field_1__c = deskList[0].Field_2__c;

            }

        }

        update deskList;

    }

}

Apex Trigger:

trigger updateFieldTrigger on Desk__c (after update) {

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

        for(Desk__c d : Trigger.new) {   

            DeskCX.updateDate(); 

        }

    }

}
  • For starters use a before trigger and you need to pass in the records instead of querying all records every time – Eric May 31 '17 at 7:07
  • 1
    Because you update every time you are causing your own trigger to be fired every time and so you are in an infinite loop. See Eric's advice that would avoid that. You also should loop over and process every record in the Trigger.new collection not query every record in the org (could be millions) and then only process the first one. – Keith C May 31 '17 at 8:35
1

The comments on your question thus far pretty much have the issue identified and the solution presented.

Your issue (well, the main one causing this error at least) is that your updateField() method is updating Desk__c records, and you call this method from a trigger on Desk__c. This starts an infinite loop, which Salesforce is detecting and stopping (presenting you with the error you see).

A solution to this issue is to make a couple of simple changes

  1. Change your trigger to be before update.
  2. Pass records into your helper method instead of querying for them inside your method
  3. Remove the dml statement from your helper method

From the documentation on triggers (emphasis mine)

There are two types of triggers:

  • Before triggers are used to update or validate record values before they’re saved to the database.
  • After triggers are used to access field values that are set by the system (such as a record's Id or LastModifiedDate field), and to affect changes in other records, such as logging into an audit table or firing asynchronous events with a queue. The records that fire the after trigger are read-only.

Since you're looking to update Desk__c records, and your helper is being called from a Desk__c trigger, you can use a before trigger to avoid needing to use DML (and thus avoid your infinite loop).

To be able to update records without using DML, you need to use the Desk__c instances stored in either Trigger.new or Trigger.newMap.

trigger

Trigger DeskExample on Desk__c (before update){
    // This trigger only runs before update for now, so there's no need to check
    // Trigger.isBefore and Trigger.isUpdate.
    // It's still good practice (and a very cheap and simple check), in my opinion,
    //   but I'll be omitting it here for brevity.

    // Also, notice that I've removed the for loop here (it'll show up in updateDate() ).
    // You certainly could keep the for loop in the trigger here, but my experience
    //   leads me to want to pass a collection of records to a method once (rather 
    //   than sending a single record and calling the method many times).

    // Trigger.new is, generally speaking, a List<SObject>.
    // However, inside of a trigger, Salesforce has enough context to know that it's a 
    //   collection of a more specific type (List<Desk__c> in this case).
    DeskCX.updateDate(Trigger.new);
}

helper

public class DeskCX {
    // The method signature here changed so it can accept a List<Desk__c>
    // Doing things this way also means that you could test this method separately
    //   from testing your trigger.
    // You'd still need to test your trigger, but if you were to test this method
    //   as an independent unit, the test setup would be very simple.
    // You'd only need to create the Desk__c records in-memory (no DML or querying
    //   required for your test setup).
    public static void updateDate(List<Desk__c> deskList){
        // Since the deskList is being passed as a parameter, there's no need to
        //   query for desks.

        // And here's that for loop that was previously in your trigger
        for(Desk__c desk :deskList){
            // Your other logic remains largely the same
            if(desk.field1__c != null && desk.field2__c != null){
                desk.field1__c = desk.field2__c;
            }

            // Just like queries inside of a loop, dml inside of a loop is bad
            // The DML update here is not required, so I've removed it
        }

        // DML update is still not required, so don't go putting that here (outside of 
        //   the loop) either.
    }
}

The reason why this will work is because it takes advantage of 2 separate features of Apex.

First, passing a collection into a method allows you to change the values contained in the collection (relevant documentation). When the method (that was passed a collection) returns, the caller does see the changes that were made. To illustrate this:

public void myTestMethod(){
    List<Desk__c> myDeskList = new List<Desk__c>();

    for(Integer i = 0; i < 10; i++){
        myDeskList.add(new Desk__c(field1__c = null));
    }

    // Debugging at this point will show you that all Desk__c records have a null value
    //   stored in field1__c
    system.debug(myDeskList);

    myOtherTestMethod(myDeskList);

    // The modifications we made have persisted after the method finishes.
    // Debugging here will verify that all records have a value of "Potato" in
    //   field1__c
    system.debug(myDeskList);
}

public void myOtherTestMethod(List<Desk__c> deskList){
    // Simply modify a field.
    for(Desk__c desk :deskList){
        desk.field1__c = 'Potato';
    }
}

The second feature that we're taking advantage of is one that I mentioned earlier, the behavior of before triggers. If you update values stored in either Trigger.new or Trigger.newMap in a before trigger, those changes will be saved without requiring DML.

These two features can be combined. By passing trigger.new into your method, it allows your method to make changes to the values stored in trigger.new. By virtue of being a before trigger, the changes made by your method (stored in trigger.new) will be saved without DML.

The one 'gotcha' with this method is that you only ever update records that are triggered. That means that your data going forward will be correct, but old, incorrect data will remain incorrect. The way around this is to run some anonymous apex to send your old records through your trigger (simply calling update someList; will work. Even if you aren't updating any data on those records, they will still go through any update triggers that are active).

// Filtering on LastModifiedDate should ensure that we don't repeatedly try to update
//   the same records over and over.
// The 'LIMIT 5000' is there for safety. We can only DML 10,000 records in a 
//   transaction, and this gives you room for other triggers/code to work (if you
//   have any).
// You may need to run this as anonymous apex more than once, depending on how
//   many records you have.
update [SELECT Id FROM Desk__c WHERE LastModifiedDate < TODAY LIMIT 5000];

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.