3

Is it possible to implement transactional DML for batches? for example we have parent object and child and if the update of child object is failed then the update of this particular parnet and child should be rolled back but the rest parent-child objects should be saved if they are updated without errors. But the difficulty is that there are chunks of such records: 10000 parents and each parent have 2 child. Is it possible to leverage System.Savepoint in clever way to support batch update

1

When you perform an update, and that update performs additional updates via a trigger, you can partially fail the transaction to perform an automatic rollback and partial retry.

Consider the following trigger:

trigger cascadePhone on Account (after update) {
    // List of numbers that were changed from
    Set<String> oldPhones = new Set<String>();

    // Find all phones that changed and record those
    for(Account record: Trigger.old) {
        if(record.Phone != Trigger.newMap.get(record.Id).Phone) {
            oldPhones.add(record.Phone);
        }
    }

    // Find all contacts that might match
    Contact[] contacts = [SELECT    Id, AccountId, Phone 
                          FROM      Contact 
                          WHERE     AccountId IN :Trigger.new AND 
                                    Phone IN :oldPhones];

    // Remove contacts that have the right phone but wrong account
    // We count backwards because we're removing records in place in the list
    // and if we counted forwards we'd skip rows
    for(Integer index = contacts.size()-1; index >= 0; index--) {
        if(contacts[index].Phone == Trigger.oldMap.get(contacts[index].AccountId).Phone) {
            contacts[index].Phone = Trigger.newMap.get(contacts[index].AccountId).Phone;
        } else {
            contacts.remove(index);
        }
    }

    // Partial update
    Database.SaveResult[] results = Database.update(contacts, false);

    // For each record that failed, mark the account as failed as well
    for(Integer index = 0; index < contacts.size(); index++) {
        if(!results[index].isSuccess()) {
            Trigger.newMap.get(contacts[index].AccountId).Phone.addError('Failed to update some phone numbers');
        }
    }

}

If you perform an update of accounts in an execute function, like this:

public static void execute(Database.BatchableContext bc, Account[] scope) {
    // Fix accounts in scope
    // Then update them
    Database.update(scope, false);

}

What's not obvious here is that the system automatically performs a rollback on all records that error out, and attempt to retry the accounts that are left. This is an implied partial callback. Nowhere am I using a SavePoint to rollback the errors; the system handles the logic for me. The child records that failed also roll back, so the contacts that were on the accounts that failed would also not update as a result of this code.

| improve this answer | |
0

If you have NO LIMIT problems (number of asynchronous Apex method executions), you can use batch size of 1, retrieve the child objects in your execute method and control the transaction in it's own context. However this may not be the best approach in the long term, especially if you expect the number of parents records to increase

| improve this answer | |
-2

There is no clever way using standard transaction control. Transaction control over multiple DML statements in SFDC is all-or-nothing.

If I read your question literally, you could opt for updating the child records first. For any child record update that fails, remove the parent from the set of parent records to update. Then update the remaining parent records. But not sure if that still covers your functional requirement.

Alternatively,

  1. Create a savepoint
  2. do the whole batch (parent+child updates), and in the meanwhile maintain a Set of any parent record ids that either fail themselves or have failed children.
  3. Rollback that entire transaction to the savepoint
  4. Do the same batch but exclude (both on parent and child level) the records that failed in the first round.
| improve this answer | |
  • You can indeed do partial saves in batch or any other Apex Code. The main problem most people have is not handling bulk transactions correctly in triggers. – sfdcfox May 7 '14 at 15:15
  • @sfdcfox I agree that you can do partial saves in apex. But can you do a partial rollbacks of an earlier update depending on the results of a later child update? I don't believe you can. If I'm wrong, please do share your approach as I'd be very interested to learn. – Guy Clairbois May 8 '14 at 8:38
  • You certainly can perform a partial rollback, although its not "obvious." I'll probably have to add an answer, as the code is non-trivial. – sfdcfox May 8 '14 at 15:32
  • @sfdcfox were you still planning to do that, as I'm very curious.. I can imagine you don't have the time to post a full answer. Maybe share the basic concepts here and I can reshape it into a full answer? – Guy Clairbois May 12 '14 at 14:57
  • I just wrote my answer that demonstrates the system's automatic rollback feature. No SavePoint is necessary as long as the children records are being updated by a trigger. – sfdcfox May 13 '14 at 4:26

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.