0

When we are using Database.insert() or Database.update() with a false option(In any service) then it returns a success or failure result. We know in the insert if any lines fail, delete the header and sibling line which is OK, but in case of an update we want to restore the previous state (not to delete).

we need a solution to rollback partially. Salesforce does not provide it natively. What it provides is entire rollback using Savepoints. But it does provides partial commits using allOrNone = true/false.

So we want to develop a solution that commits partially using Database.insert/update (allOrNone as false). And then we put a savepoint before firing this DML, and then we rollback to this Savepoint without loosing the successfully committed data. We just want to rollback the bad records chain that could not go through. We are OK with insert even without a proper solution (we can just delete them). But updates and deletes are pain point. Deletes can not be undeleted so easily (triggers, and the entire system is not very supportive of undelete). Same with updates. We don't know what target state to rollback if we have to do it manually. Solution approach we thought of- We set savepoint. -> fire DML -> fail and rollback -> filter out the records that caused errors and reattempt the DML with happy records. But this approach will actually breakdown. See, error happens in least scenarios. So if we are reverting bulk transaction for a few handful errors, and then retrying it then it increases the limits by 3X (X original + X rollback + X retry) depending on where we got the error and at what point of flow in the entire transaction, we plan to rollback (we may want to accumulate error until a break point beyond which we think we can not handle things)

1 Answer 1

0

If you're using this logic in triggers, this is handled automatically. Simply call addError on the trigger record if a related record fails, and it will naturally cascade up the chain. You don't even have to filter out the failures manually. Consider this trivial example:

trigger X on Account(after update) {
  Contact[] contacts = [SELECT AccountId FROM Contact WHERE AccountId = :Trigger.new];
  for(Contact contactRecord: contacts) {
    // Do something here
  }
  Database.SaveResult[] results = Database.update(contacts, false);
  for(Integer i = 0; i < results.size(); i++) {
    if(!results[i].isSuccess()) {
      Trigger.newMap.get(contacts[i].AccountId)
        .addError('Cascading error: '+results[i].getErrors()[0].getMessage());
    }
  }
}

Here, the caller performs some DML on Account records, and they enter the save process. At this point, we try some DML on Contacts, and if there are no errors, the transaction completes successfully. If any contact fails, we rollback and are given a new trigger context with the remaining records. Even better, we also rollback the DML and SOQL limits back to before the DML on the Account list started.

If you need to use a design within a single unit, like a Visualforce page, then unfortunately, you do need to deal with using a lot of DML statements. It's usually easier to just roll back as soon as you fail and report the error to the user, rather than try to perform partial updates that are tricky.

You must log in to answer this question.

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