[SalesForce] addError() & Database.update(obj[], false)

How should I efficeintly handle addError() errors when using Database.update(obj[],false) from the code?

They are all failing as they are hitting addError() in all of the 3 attempts. Is Database.update(obj[], false) not suitable with addErrors()?

After working on the issue for quite some time, I decided to use a boolean to ignore the validation errors so as not to encounter addError() from the trigger.
Is this the correct approach?

Let's take an example of 5 records out of which the first 3 consecutive records throw addError().

  • Database.update(obj[],false) First attempt: hits the error at the 1st record and deletes it from the array of records for retrying. The array for the retrial has 4 records out of which 2 would fail.
  • Database.update(obj[],false) Second attempt: Fails again at the first record and deletes it. The array now has 3 records out of which 1 would fail again.
  • Database.update(obj[],false) Third attempt: Fails again at the first record and rollsback the updates on the whole array as Database.update(obj[], false) only tries 3 attempts before failing.

Best Answer

SObject.addError accumulates all errors in the same trigger context. In reality, properly written triggers would each add their errors, and there would be ideally at most one retry. If it's not working that way, there's a problem with your code. Here's a trivial example that you can copy-paste into a developer org to prove that addError certainly works with more than 3 records:

trigger prevent1358 on account (before insert) {
  if(Trigger.new.size() == 8) {
    Trigger.new[0].addError('You have been blocked.');
    Trigger.new[2].addError('You have also been blocked.');
    Trigger.new[4].addError('Sorry. You are BLOCKED.');
    Trigger.new[7].addError('So sorry. You. Are. Blocked. Ha!');
  }
}

Then, in execute anonymous:

Account[] records = new Account[8];
for(Integer index = 0; index < 8; index++) {
  records[index] = new Account(Name='Account '+(index+1));
}
Database.SaveResult[] results = Database.insert(results, false);
System.assertEquals(false, results[0].isSuccess());
System.assertEquals(false, results[2].isSuccess());
System.assertEquals(false, results[4].isSuccess());
System.assertEquals(false, results[7].isSuccess());

This behavior is true even if the addError messages occur in different triggers (but you shouldn't have different triggers, using the One Trigger Per Object best practice).

Do not write your code that only checks one error at a time. The following trigger will break if you have multiple errors:

trigger preventRecord0 on account (before insert) {
  Trigger.new[0].addError('Blocking the first record.');
}

Account[] records = new Account[8];
for(Integer index = 0; index < 8; index++) {
  records[index] = new Account(Name='Account '+(index+1));
}
// Too many retry errors
Database.SaveResult[] results = Database.insert(results, false);

While it may make sense to try and break early on an error, or only do one type of validation at a time, you're actually setting yourself up for failure. Perform all of your validations once and in the same location.