3

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?

  • One question: Are you sure that they are inserting that custom object using normal insert? Because my guess is that it's not entirely true OR they have some exception handling with finally implemented – user1974566 Jul 23 '18 at 20:25
  • 1
    Do you have more code to show? It's hard to see what's going on from what you are saying – Caspar Harmer Jul 23 '18 at 20:28
  • Yes, they are inserting the custom object and not calling the database action. Other than instantiating the values to load, there isn't anything else going on in the apex class. The actual error they are receiving is happening in the after insert trigger they have, where they are loading a related Opportunity, but it is all happening within the context of the After Insert action for the custom object. – Tezyn Jul 23 '18 at 21:25
  • are you sure the second database event isn't occurring in an async (future or queueable) transaction spawned by the first action? – cropredy Jul 24 '18 at 0:20
  • @copredy Yes. Running a Debug session shows both the first DML Insert ending successfully, and the error that occurs during the After Trigger on the second one, in the same log file. – Tezyn Jul 24 '18 at 1:04
4

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).

  • Thanks for the response. I know they don't have the second insert in a try catch block. I have run a debug log on the transaction, and the transaction fails with an INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY error, so I don't believe they are catching the actual error, but I will have to fully trace the execution to say for sure – Tezyn Jul 23 '18 at 22:30
  • @Tezyn Unless you're using platform events, which could also complicate things. Platform events can't be cancelled, which could allow for the "partial transaction" scenario as well. – sfdcfox Jul 23 '18 at 22:48
2

I can think of at least two scenarions when record will be inserted even when DMLException SHOULD (in one case it will it other wont) occur.

First

Exception handling

Account a1 = new Account();
a1.Name ='NewTest4';
a1.Name2__c = 'NewTest2';

Account a2 = new Account();   

a2.Name ='NewTest4';
a2.Name2__c ='NewTest2';


try{
    insert a1;
    insert a2;
} catch (DMLException e){
    System.debug(e);
} finally {
     insert new Case();
}

Second Database.Insert

Account a1 = new Account();
a1.Name ='NewTest4';
a1.Name2__c = 'NewTest2';

Account a2 = new Account();

a2.Name ='NewTest4';
a2.Name2__c ='NewTest2';


List<Account> accs = new List<Account>();

accs.add(a1);
accs.add(a2);

Case c = new Case();

Database.insert(accs, false);
insert c;

Name2__c is unique in my case.

Database.insert with second parameter set to false indicates that it allows partial insert (all of the correct Accounts will be inserted, none of incorrect ones)

insert(recordsToInsert, allOrNone) Adds one or more sObjects, such as individual accounts or contacts, to your organization’s data.

Signature public static Database.SaveResult[] insert(sObject[] recordsToInsert, Boolean allOrNone)

Parameters recordsToInsert Type: sObject [] allOrNone Type: Boolean

The optional allOrNone parameter specifies whether the operation allows partial success. If you specify false for this parameter and a record fails, the remainder of the DML operation can still succeed. This method returns a result object that can be used to verify which records succeeded, which failed, and why. If the parameter is not set or is set true, an exception is thrown if the method is not successful.

In both cases first account will be inserted and case also will be inserted.

  • thanks @user1974566 I have confirmed that the execution isn't in a try/catch or database.insert(xxxx,false) condition. – Tezyn Jul 24 '18 at 14:41

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.