2

Today I am writing an apex class to transform a bulk set of data from one record to another. During the course of this transfer, it is expected that we will have errors on the records in the system. What we would like to do if an error is found, is mark the record as error, and add the exception that caused the error to field on the record. We would then like the class to continue to process records.

What I am unable to figure out at this time, is how do I capture the exception of the record in error, and toss it into our error bucket, without stopping the process of the remainder of the records or hitting any limits?

My first thought was to insert all the records into a list, and then go through each record attempting an insert in a try catch block. I then realized this would still be in danger of hitting our SOQL limits as we would have to do individual inserts for every record in the list. On the other hand, if I attempt to do an insert of the entire list at once in a try catch block, we will not be able to capture the error on the relevant record that I can think of, and the processing will stop upon the failure of any record.

With those two ideas both out, I am struggling to come up with a way to design this system to fulfill both of the needs outlined above. I am hoping that perhaps someone with a little better understanding of the exception system can provide me an alternative design that can store individual exceptions on the records that caused them without stopping processing the remainder or risking running into limits.

3

To Begin with what you're looking for is a partial insert instead of all or nothing and be able to report back on individual record level errors or exceptions.

Database.insert(recordsToInsert, allOrNone): Adds one or more sObjects, such as individual accounts or contacts, to your organization’s data. You can achieve partial inserts using this above method and specifying allorNone as FALSE.

recordsToInsert Type: sObject []

allOrNone Type: Boolean

The optional allOrNone parameter specifies whether the operation allows partial success. If you specify false for this parameter and a record fails, the remainder of the DML operation can still succeed. This method returns a result object that can be used to verify which records succeeded, which failed, and why. If the parameter is not set or is set true, an exception is thrown if the method is not successful.

Exceptions in Apex Exceptions note errors and other events that disrupt the normal flow of code execution. throw statements are used to generate exceptions, while try, catch, and finally statements are used to gracefully recover from exceptions.

There are many ways to handle errors in your code, including using assertions like System.assert calls, or returning error codes or Boolean values, so why use exceptions? The advantage of using exceptions is that they simplify error handling. Exceptions bubble up from the called method to the caller, as many levels as necessary, until a catch statement is found to handle the error. This bubbling up relieves you from writing error handling code in each of your methods. Also, by using finally statements, you have one place to recover from exceptions, like resetting variables and deleting data. Also read more about List of all possible APEX Exceptions

SaveResult Class An array of SaveResult objects is returned with the insert and update database methods. Each element in the SaveResult array corresponds to the sObject array passed as the sObject[] parameter in the Database method, that is, the first element in the SaveResult array matches the first element passed in the sObject array, the second element corresponds with the second element, and so on. If only one sObject is passed in, the SaveResult array contains a single element.

The following example shows how to obtain and iterate through the returned Database.SaveResult objects. It inserts two accounts using Database.insert with a false second parameter to allow partial processing of records on failure. One of the accounts is missing the Name required field, which causes a failure. Next, it iterates through the results to determine whether the operation was successful or not for each record. It writes the ID of every record that was processed successfully to the debug log, or error messages and fields of the failed records. This example generates one successful operation and one failure.

// Create two accounts, one of which is missing a required field
Account[] accts = new List<Account>{
    new Account(Name='Account1'),
    new Account()};
Database.SaveResult[] srList = Database.insert(accts, false);

// Iterate through each returned result
for (Database.SaveResult sr : srList) {
    if (sr.isSuccess()) {
        // Operation was successful, so get the ID of the record that was processed
        System.debug('Successfully inserted account. Account ID: ' + sr.getId());
    }
    else {
        // Operation failed, so get all errors                
        for(Database.Error err : sr.getErrors()) {
            System.debug('The following error has occurred.');                    
            System.debug(err.getStatusCode() + ': ' + err.getMessage());
            System.debug('Account fields that affected this error: ' + err.getFields());
        }
    }
}

Now looking at the above example what you should be doing is use database.insert to allow partial inserts and leverage saveresult class to report any errors back with some positive exception handling. Do remember that exceptions caused by governer limts cannot be caught and is going to disrupt your flow.

  • 1
    This is hugely informative and should solve my issue perfectly! Thank you for taking the time to write all of this up. – Frank Evers Dec 10 '18 at 15:52
  • @FrankEvers Your welcome. Glad I was able to help! – codeyinthecloud Dec 10 '18 at 16:04
6

Briefly, what you need to do is switch from using update DML statements to Database.update() methods, because the latter allow you to specify allOrNone=false. Doing so means that the method will always complete (no DML exception or transaction rollback) and will return errors to you for inspection and handling.

There's useful details at DML Statements vs. Database Class Methods.

In your batch class, what you might do is something like this:

void execute(Database.BatchableContext bc, List<Account> scope) {
    for (Account a : scope) {
        // Mutate `a` here.
    }

    List<Database.SaveResult> srs = Database.update(scope, false); 
    // `false` is the `allOrNone` option

    List<Account> errorsToUpdate = new List<Account>();
    for (Database.SaveResult sr : srs) {
        if (!sr.isSuccess()) {
            String error = '';
            for (Database.Error err : sr.getErrors()) {
                error += String.valueOf(err.getStatusCode()) + ': ' + err.getMessage() + '\n');
            }
            errorsToUpdate.add(new Account(Id = sr.getId(), My_Error__c = error));
        }
    }

    // Persist error details, throwing an exception if the DML fails.
    update errorsToUpdate;
}

Essentially, you perform your error-prone operation, requesting error details be returned to you rather than an exception being thrown. Then, you process those error details into a further bulk update to your records. You may wish, as I've shown here, for that update to fail with an exception, rolling back the transaction if you're unable to persist the error details.

  • 1
    Exactly what I was looking for, much appreciated David! – Frank Evers Dec 10 '18 at 15:50

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.