0

I am performing a bulk upsert with a large batch (sometimes as much as 3000 records at one time). I am splitting the list of 3000 into 5 batches and performing an upsert using the Bulk API.

When I do this, I am causing an after update/after insert trigger to execute on each of the records.

My question is the following: If an exception occurs in the trigger for one of the records, how do I add it to the 'Error' column of the batch job results CSV file and continue processing the rest of the records? What is currently happening is I am not catching any exceptions, so if one occurs then that error is listed for every single row in the batch. I do not see a way to catch a single exception and then add it to the results file so that the trigger can continue processing the rest of the records.

2
  • It seems that my best option might be to create a custom object for logging errors like this and use the REST API to retrieve those records after the trigger executes...
    – atmdd
    Commented Oct 22, 2014 at 14:44
  • Kind of depends on what you are trying to do. For example a DMLException resulting from cross-object update can often be mapped back to the source of the error. A NullPointerException should usually be refactored with a guard clause or some other strategy. You should always know what specifically you are trying to catch and avoid the "Pokemon catch" (gotta catch em all!)
    – Adrian Larson
    Commented May 26, 2015 at 23:27

2 Answers 2

1

Based on your comments, if you have a relationship between the two objects the error mapping is fairly straightforward.

Children

public static void createChildren(List<Parent__c> parents)
{
    List<Child__c> children = new List<Child__c>();
    for (Parent__c parent : parents)
    {
        Child__c child = new Child__c();
        child.Parent__c = parent.Id;
        children.add(child);
    }
    try
    {
        insert children;
    }
    catch (DmlException dmx)
    {
        Map<Id, Parent__c> parentMap = new Map<Id, Parent__c>(parents);
        for (Integer i = 0; i < dmx.getNumDml(); i++)
        {
            Id parentId = children[dmx.getDmlIndex(i)].Parent__c;
            parentMap.get(parentId).addError(dmx.getDmlMessage(i));
        }
    }
}

Parents

public static void updateParents(List<Child__c> children)
{
    Map<Id, Parent__c> parentMap = new Map<Id, Parent__c>();
    for (Child__c child : children)
    {
        Parent__c parent = new Parent__c(Id=child.Parent__c); // and updates
        parentMap.put(child.Parent__c, parent);
    }
    List<Parent__c> parents = parentMap.values();
    try
    {
        update parents;
    }
    catch (DmlException dmx)
    {
        Map<Id, List<Child__c>> parentToChildren = new Map<Id, List<Child__c>>();
        for (Child__c child : children)
        {
            if (!parentToChildren.containsKey(child.Parent__c))
                parentToChildren.put(child.Parent__c, new List<Child__c>());
            parentToChildren.get(child.Parent__c).add(child);
        }
        for (Integer i = 0; i < dmx.getNumDml(); i++)
        {
            for (Child__c errorChild : parentToChildren.get(parents[i].Id))
            {
                errorChild.addError(dmx.getDmlMessage(i));
            }
        }
    }
}

It might be worth adding a utility that will group the children by parent, i.e. static Map<Id, List<Child__c>> groupByParent(List<Child__c> children).

I also am not sure which is better but often I use dmx.getMessage() instead of dmx.getDmlMessage(i).

0

Exceptions need to be reported for all rows when an exception occurs because your entire transaction failed, and thus nothing you did persisted. No record in the transaction was created, updated, or deleted. You must write your code to be impervious to failures.

This is the point of test methods, and it is 100% possible to ensure that your code is immune to everything but governor limits. You must properly handle all errors, Don't use try-catch blocks if you can avoid it. If you do, it can cause corrupt data. Imagine a loop half-way through that has an exception that you catch. Your data would then be half-updated and half not. This is a bad way to handle random exceptions.

Use safe DML options (with allOrNone set to false), and roll up any errors. It's a hard discipline, but it can be done. If your code reaches a point where it breaks governor limits, you must fix those problems.

2
  • Scenario: A row is successfully upserted in my bulk import, but I've decided that sufficient conditions do not exist to create the objects that split out this data (it's an integration from one system to another), or an exception occurs when splitting out that one row into the objects I want to split it into. You are saying it's bad to have that one record fail and let the rest of the transaction continue? And you are saying that I can achieve such an immunity using test methods that it is reasonable to write the code in a way that one exception prevents the entire transaction?
    – atmdd
    Commented Oct 23, 2014 at 13:22
  • There shouldn't be exceptions that bubble all the way up. Report an error on a record using addError if you want the bulk to work the way you expect. Your code can use partial saves to allow bulk transactions to partially succeed. I'll see about expanding this answer when I get a moment.
    – sfdcfox
    Commented Oct 23, 2014 at 14:39

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .