0

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=xxx, ...}

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?

  • Map keys as strings are case sensitive (one exception being the fields map returned by the schema API). Are you sure you have not added to the map with "insert" as the key then read using "Insert"? If you do this, map.get('Insert') will return null. – Phil W Oct 16 at 10:49
  • Thanks for the reply. Everything is correct on that side, otherwise it would return "List is empty: insert" as it would fail the null check for the List as there would be none. However it passes records from this list correctly and returns a couple of success lines via the system.debug in the log. – Cmd_Keen Oct 16 at 10:57
  • OK, great. You need to edit your question to remove this possible confusion since you have "Insert" and "insert" (along with "upadate" typo). Details are important when you face a technical issue and want help! :) – Phil W Oct 16 at 11:27
  • If your logging shows IDs and then you cannot find those records later, this suggests that some later processing is causing the entire transaction to be rolled back. Do you have some validation rules or automations against the triggering object that could be causing such behaviour? – Phil W Oct 16 at 11:30
  • Thx for the hint. I corrected it. In regard of further validation rules etc. I will check again and come back. However I have to mention here that the "new record" to be created is not of type the trigger object is, but rather its sharing object type. – Cmd_Keen Oct 16 at 11:38
0

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.

| improve this answer | |

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.