The challenge when implementing logic in trigger context in Salesforce is that the logic will always run when DMLs occur on the object. This often leads to the fact that code that is not supposed to be executed is executed. This can cause “Apex CPU time limit” and “Too many SOQL errors” especially for central objects (Opportunity, Lead, Account etc) where there is usually a lot of logic implemented in trigger context. Having code in trigger context many times becomes an exercise in making sure that it is not run. Consider moving the code to controller classes instead where the code is executed for a particular business event.
This can easily be observed if putting the debug logs on and creating / updating a Lead, Opportunity or any other central object. Open the debug log and filter for example for all SOQL queries executed in the transaction. Most likely there will be a lot of unnecessary SOQLs run. This will slow down the whole org (including running tests).
To fix this issue make sure that only the necessary SOQLs (and code in general) are run for the implemented logic.
For example if some logic only needs to run for a certain user or profile (utilizing a hierarchy custom setting) make sure to check for this and if it does not match then return from the method so that possible SOQL executions are avoided, hence “Exit / Return early”.
Find an example of a method called by a trigger that utilize early return (for enabled/disabled config) and utilize best practice filtering the records need processing and do early return if no records to process:
void methodCalledFromTrigger(List<Opportunity> triggeredRecords) {
// Check if user/profile is enabled for this functionality
MyCustomSetting__c setting = MyCustomSetting__c.getInstance();
if (!setting.MyMethodEnabled__c) {
return;
}
// Find all records to process (created filtered list). If no records to process, return early
Map<Id, Opportunity> opportunitiesToProcess = new Map<Id, Opportunity>();
for (Opportunity opp : triggeredRecords) {
if (opp.WhateverField__c == 'Process Me') {
opportunitiesToProcess.put(opp.Id, opp);
}
}
if (opportunitiesToProcess.isEmpty) {
return;
}
// Prepare Sets, Lists and Maps before processing takes place.
// E.g. create a cache with data
// But only for the filtered opptys to process!
Set<Id> accountIds = new Set<Id>();
for (Opportunity opp : opportunitiesToProcess.values()) {
accountIds.add(opp.AccountId);
}
Map<Id, Account> accountsById = new Map<Id, Account>([
SELECT Id, Name, AccountNumber, MySpecialField__c
FROM Account
WHERE Id IN :accountIds
]);
// Do the processing on the records using the prepared Sets, List and Maps, e.g. cached data
List<Case> casesToCreate = new List<Case>();
for (Opportunity opp : opportunitiesToProcess.values()) {
Account relatedAcc = accountsById.get(opp.AccountId);
if (relatedAcc.MySpecialField__c) {
casesToCreate.add(new Case(
Opportunity__c = opp.Id,
AccountId = relatedAcc.Id,
Subject = WHAT_EVER_SUBJECT
));
}
}
// Perform DML operations
if (!casesToCreate.isEmpty()) {
insert casesToCreate;
}
}

Leave a comment