[SalesForce] Partial Execution within apex function

A customer of mine is seeing partial execution of an Apex Function.

In the Function, they need to Insert/Update multiple objects.

Insert new Contact(...);
...
Insert new Cust_object__c(...);

In the second insert, they are encountering an error, but the Contact from the first insert is remaining, and I am trying to understand why.

With how I am reading the documentation around transaction Control, because the entire apex isn't finishing, the entire transaction shouldn't be committed.

Transaction Control Documentation

If the entire request completes successfully, all changes are
committed to the database. For example, suppose a Visualforce page
called an Apex controller, which in turn called an additional Apex
class. Only when all the Apex code has finished running and the
Visualforce page has finished running, are the changes committed to
the database.

I understand that I can create a save point, and roll back once the error is encountered, but I am trying to understand the Apex execution rules.

Shouldn't all transactions within the function be rolled back if there is an error encountered, even DML statements that completed successfully?

Update:

I have confirmed that there isn't any code that is captured in Try/Catch blocks. All DML operations are using DML "Insert" and not Database.insert(xxx,false). The underlying error stemmed from hard coded IDs that referenced objects that were deleted, but I still need to understand why only part of the transaction was rolled back.

The execution stack:

Lightning component Calls Apex Controller @AuraEnabled Function
    Apex method Inserts Contact
    Apex method Inserts Custom Object
        Before Insert Trigger
        After Insert Trigger
            Apex Custom Trigger Helper Class
                Insert Opportunity
                    Before Insert Trigger
                    After Insert Trigger
                        Apex Custom Opportunity Trigger
                            (Error Occurs)

The Insert for the Contact remains. The Insert of the Custom Object and the triggered Insert of the Opportunity are both rolled back.

Why wouldn't all DML actions in the Controller method be rolled back?

Best Answer

As long as the transaction completes successfully, any DML operations that completed successfully will remain so. Usually, this means that a developer did something like this:

try {
  ...
  insert firstList;
  ...
  insert secondList;
  ...
} catch(Exception e) {
  System.debug(e.getMessage());
}

Since they caught the exception, the transaction completed successfully; if firstList completed okay but secondList threw an exception, then this pattern would result in a partial transaction like you describe.

The general rule is that you should not do this; always roll back the transaction if you want to use try-catch atomically, or don't catch the exception.

The general pattern for rollback is like this:

Database.SavePoint sp = Database.setSavePoint();
try {
  ...
  insert firstList;
  ...
  insert secondList;
  ...
} catch(Exception e) {
  Database.rollback(sp);
  ApexPages.addMessages(e);
}

This will prevent partial changes from being saved. Alternatively, don't use try-catch at all, but be aware that if you do this in a Visualforce page, it can result in a loss of "view state" when the page crashes, meaning the users won't have an opportunity to correct the error(s).

Related Topic