Apex: Avoid DML when records are unchanged

apexdmlperformancetrigger

I have noticed that many of the Apex triggers in my org update records even when there are no substantive changes to those records. For instance, consider a trigger that sets field values on an object based on some criteria, then makes an update call to commit those values to the database. In practice, this trigger commonly assigns the exact same values that already exist on the record, so it ends up making a DML call when no DML is needed — or including more records in an update list than need to be included.

To improve performance across the org, I would like to retrofit all my triggers to avoid these kinds of inefficiencies, probably via some type of utility method. Has anyone else has tackled this kind of problem, who can recommend an efficient pattern to address it?

Note: I understand that it is possible to check existing field values one by one, but to me this seems like an unacceptably verbose approach.

Best Answer

You can do this with a couple of maps. I guess a nice convenience method would help.

public static void getDiffs(sObject[] recordsToUpdate, Map<Id, sObject> preValues, Map<Id, sObject> postValues) {
  for(Id recordId: postValues.keySet()) {
    sObject postValue = postValues.get(recordId);
    if(preValues.get(recordId) != postValue) {
      recordsToUpdate.add(postValue);
    }
  }
}

Which then lets you write something like:

Map<Id, Account> oldValues = new Map<Id, Account>([
  SELECT ... FROM ... WHERE ...
]);
Map<Id, Account> newValues = oldValues.deepClone();
// Now, manipulate newValues as you'd like
// Then
Account[] recordUpdates = new Account[0];
getDiff(recordUpdates, oldValues, newValues);
update recordUpdates;

When you use != to compare sObjects, it checks all fields at once, so by setting up specific "old values" to compare to, you can check many, even hundreds, of fields at once.

Note that I specifically use an "out parameter" syntax because of various language limitations. For example, you can't use upsert on a List<sObject>, so we can select a more specific ("concrete") sObject type.

Related Topic