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
Attribution to: Natallia
Possible Suggestion/Solution #1
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
Attribution to: ManSpan
Possible Suggestion/Solution #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,
- Create a savepoint
- 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.
- Rollback that entire transaction to the savepoint
- Do the same batch but exclude (both on parent and child level) the records that failed in the first round.
Attribution to: Guy Clairbois
Possible Suggestion/Solution #3
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.
Attribution to: sfdcfox
This content is remixed from stackoverflow or stackexchange. Please visit https://salesforce.stackexchange.com/questions/34545