+1 vote
12 views
by (163k points)

We have a Trigger Handler which calls on "after update" a method "CreateNewRecords".

public without sharing class sObjectTriggerHandler {
    public static void handleAfterUpdate(Map<Id, Contact> newMap, Map<Id, Contact> oldMap) {
        createNewRecord(newMap, oldMap);
    }
    private static void createNewRecord(Map<Id, Contact> newMap, Map<Id, Contact> oldMap) {
        ...
        CreateNewRecordUtils newRecordUtils = new CreateNewRecordUtils();
        ...
        newRecordUtils.insertRec(recListMap.get('Insert'));
    }
}

Within this method, a new instance of a helper class called "CreateNewRecordUtils" is generated.

        CreateNewRecordUtils newRecordUtils = new CreateNewRecordUtils();

Amongst others, this helper class "CreateNewRecordUtils" has three methods called "insertRec", "deleteRec" and "updateRec", having basically same structure looking something like

public class CreateNewRecordUtils {
...
public void insertRec(List<sObject> recs) {
    if (rec == null || recs.isEmpty()) {
        System.debug('List is empty: insert');
        return;
    }
        List<Database.SaveResult> recs = Database.insert(recs, false);
        Integer i = 0;
        for (Database.SaveResult sr : srs) {
            if (!sr.isSuccess()) {
                for (Database.Error err : sr.getErrors()) {
                    System.debug('Error on insert: ' + recs[i] + ':' + err);
               }
            } else {
                system.debug('Record insert succeeded: ' + recs[i]);
            }
            i++;
        }
}
public void deleteRec(List<sObject> recs) {...}
public void updatetRec(List<sObject> recs) {...}
...

}

"Database.insert" has allOrNothing on false. After creating the instance "newRecordUtils" those three utils methods are called in the "CreateNewRecords" method, e.g. for insert:

newRecordUtils.insertRec(recListMap.get('Insert'));

The "recListMap" is a Map declarated with Map<String, List> where as String there is either set "Insert", "Delete" or "Update". For the insert example above, it will then just grab the list which has as a String tag "Insert". Same goes for other two methods "update" and "delete".

In the log it actually shows

Record inserted successfully: sObject:{Id=***, ...}

However, the records are not created apparently, as they cannot be found on the "database", e.g. via SOQL.

Is it possible that even tho we do not use constructors here, there is a similar issue when instantiating this utils class and call its method as it is described here? Why is DML not allowed in Constructor?

1 Answer

+1 vote
by (163k points)
 
Best answer

So thanks to the suggestions, we were able to find the root cause.

Unfortunately I was not specific about the records to be inserted in the introduction part. The records to be inserted are sharing records.

It looks like Salesforce will delete all manual sharing record once record Owner is being changed: https://trailblazer.salesforce.com/ideaView?id=08730000000BqA4

We have a Process Builder in place, which will change (back) the record owner based on the linked account owner.

According to the order of execution in Salesforce, process builder is executed after "After-Trigger": https://www.asagarwal.com/order-of-execution-rules-triggers-etc-in-salesforce-debug-log/.

Since we insert new sharings in the trigger section already, all the sharing will be deleted whenever this Process Builder fires and changes the owner.

Solution was to move this Process builder logic into apex to the Before-Trigger.

Welcome to Memory Exceeded, where you can ask questions and receive answers from other members of the community.
...