Skip to main content
replaced http://salesforce.stackexchange.com/ with https://salesforce.stackexchange.com/
Source Link

There's a lot to unpack about how the above was written to consume just one query and one dml operation. About the query limits, you did consume a second query by getting the children, but this type of sub-query consumes a separate governor limitseparate governor limit. Usually this "aggregate query" limit is not one you have to worry about overly much.

There's a lot to unpack about how the above was written to consume just one query and one dml operation. About the query limits, you did consume a second query by getting the children, but this type of sub-query consumes a separate governor limit. Usually this "aggregate query" limit is not one you have to worry about overly much.

There's a lot to unpack about how the above was written to consume just one query and one dml operation. About the query limits, you did consume a second query by getting the children, but this type of sub-query consumes a separate governor limit. Usually this "aggregate query" limit is not one you have to worry about overly much.

Source Link
Adrian Larson
  • 151.3k
  • 38
  • 247
  • 431

Here's my opinion on what "best practice" is for trigger handlers.

  • Your Trigger should only answer when questions.
  • What event is firing?
  • Your Handler should only answer what/which questions.
  • What operation(s) to perform?
  • Which records to act on (criteria)?
  • Your Service should only answer how questions.
  • How to perform each action?
  • How to apply each filter (criterion)?

Each layer will pass stateful information (the trigger records) down as needed. In my observation, the term Helper class is not used rigorously as these terms above.

In your scenario, all your logic can be written in a CaseService class. Something like the following:

public with sharing class CaseService
{
    public static void updateChildAccounts(List<Case> records)
    {
        Set<Id> accountIds = new Set<Id>();
        for (Case record : cases) accountIds.add(record.AccountId);

        List<SObject> recordsToUpdate = new List<SObject>();
        for (Account child : [
            SELECT Id, (SELECT Id FROM ABCs__r) FROM Account
            WHERE Id IN :accountIds
        ]){
            // set account fields
            recordsToUpdate.add(child);
            
            for (ABC__c grandchild : child.ABCs__r)
            {
                // set ABC__c fields
                recordsToUpdate.add(grandchild);
            }
        }

        recordsToUpdate.sort();
        update recordsToUpdate;
        // error handling strongly recommended, but omitted here for brevity
    }
}

There's a lot to unpack about how the above was written to consume just one query and one dml operation. About the query limits, you did consume a second query by getting the children, but this type of sub-query consumes a separate governor limit. Usually this "aggregate query" limit is not one you have to worry about overly much.

The next thing to understand is that you can Create Records for Different Object Types. You can insert up to ten different types, but if you alternate back and forth between Account and ABC__c, each chunk counts towards that maximum. That's where sort comes in, and thankfully the first step in the sort sequence is to check the type of sObject. So after you call sort you're back down to two chunks and you're good to go!

As for exception handling, you should read up on how to best handle a DmlException. I'm having a surprisingly hard time finding any good resources to link at the moment, but I'll try to come back and add it in if I find one.

My basic pattern for the rest would look like:

Trigger

trigger CaseTrigger on Case (before insert)
{
    CaseTriggerHandler handle = new CaseTriggerHandler(trigger.new, trigger.oldMap);
    if (trigger.isBefore)
    {
        if (trigger.isInsert) handle.beforeInsert();
        if (trigger.isUpdate) handle.beforeUpdate(); // if you needed it
    }
    if (trigger.isAfter) // if you needed it
    {
        // etc.
    }
}

Handler

public with sharing class CaseTriggerHandler
{
    @TestVisible static Boolean bypassTrigger = false;
    final List<Case> newRecords;
    final Map<Id, Case> oldMap;
    public CaseTriggerHandler(List<Case> newRecords, Map<Id, Case> oldMap)
    {
        this.newRecords = newRecords;
        this.oldMap = oldMap;
    }
    
    public void beforeInsert()
    {
        if (bypassTrigger) return;
        
        CaseService.updateChildAccounts(newRecords);
    }
    public void afterInsert() { /*if needed*/ }
    
    public void beforeUpdate() { /*if needed*/ }
    // etc.
}