1. Bulkified Triggers:
Bulkifying triggers means writing apex triggers using bulk design pattern so that triggers have better performance and consume less server resources.
As a result of it bulkified code can process large number of records efficiently and run within governor limits on force.com platform.
The main characteristics of bulkified code is:
- Operating on all records of trigger.
- Performing SOQL & DML on collections of sObjects instead of single sObject at a time.
- Using maps to hold query results organized by record id. Avoid query within a query and save records in map which later can be accessed through map rather than using SOQL.
- Using Sets to isolate distinct records.
trigger ApexTrigger on Lead (before insert)
{
/**** Bulkified Triggers ****/
Lead l = Trigger.new[0]; // will only update the value for first record
l.Rating = ‘Warm’; //Avoid using Triggers like this
//Always use Triggers in Bulkified form
for(Lead l : Trigger.new) // will iterate through all the new records
{
l.Rating = ‘Warm’;
}
}
Bulkified Triggers – Coding practices:
Example 1:
Avoid creating Triggers that work only for individual records but not for entire datasets:
trigger testTrigger on Acount__c (before insert) {
Acount__c acc = Trigger.New[0];
acc.Address__c = ‘Temporary Address’;
}
Create Triggers that use loops to help iterate over a list of records within a transaction:
trigger testTrigger on Acount__c (before insert) {
integer i = 1;
for(Acount__c acc : Trigger.new){
acc.Address__c = ‘Test Address ‘+i;
i++;
}
}
Example 2:
In this code, let’s assume 200 Account records are updated, so the “for” loop would iterate over 200 records.
trigger BranchTrigger on Branch__c (before update) {
for(Branch__c br : Trigger.new){
List<Acount__c> accList = [SELECT Name, Account_Name__c, Address__c,
Balance__c FROM Acount__c
WHERE Acount_of_Branch__c = :br.id];
System.debug(accList);
// Perform specified logic with queried records
}
}
Now let’s look at a good example of querying bulk data and iterating it.
trigger BranchTrigger on Branch__c (before update) {
List<Acount__c> accList = [SELECT Name, Account_Name__c, Address__c,
Balance__c FROM Acount__c
WHERE Acount_of_Branch__c IN :Trigger.New];
System.debug(accList);
for(Acount__c acc : accList){
// Perform specified logic with queried records
}
}
Example 3:
DML statements are also bound by Governor Limits; you can call only 150 DML operations in a transaction.
trigger BranchTrigger on Branch__c (before update) {
List<Acount__c> accList = [SELECT Name, Account_Name__c, Address__c,
Balance__c FROM Acount__c
WHERE Acount_of_Branch__c IN :Trigger.New];
System.debug(accList);
integer i = 0;
for(Acount__c acc : accList){
acc.Address__c = ‘Test Address ‘+i;
i++;
update acc;
}
}
Let’s look a correct coding example where we have instantiated another Account object list called “accToUpdate.”
trigger BranchTrigger on Branch__c (before update) {
List<Acount__c> accToUpdate = new List<Acount__c>();
List<Acount__c> accList = [SELECT Name, Account_Name__c, Address__c,
Balance__c FROM Acount__c
WHERE Acount_of_Branch__c IN :Trigger.New];
System.debug(accList);
integer i = 0;
for(Acount__c acc : accList){
acc.Address__c = ‘Test Address ‘+i;
i++;
accToUpdate.add(acc);
}
if(!accToUpdate.isEmpty()){
update accToUpdate;
}
}
2. Trigger Helper Class Pattern:
According to “Best Practices” suggested by Salesforce, we should always use a Helper Class (Apex Class) with a Trigger.
It is a design pattern which makes it easy to maintain the code in the long term.
Common Avoidable Practice:
Salesforce record changes → Trigger containing all the performing code, executes → End
Best Practice:
Salesforce record changes → Trigger calls out to one or multiple classes → Class contains the performing code which executes → End
Example:
Let’s understand the concept of Trigger Helper Class Pattern with an example of a Trigger on Account object, which fires for all the Trigger events.
trigger accUpdate on Account (before insert, after insert, before update, after update)
{
if(Trigger.isBefore)
{
If(Trigger.isInsert) {
// execute first trigger
AccTriggerHelper.firstMethod(Trigger.new);
// execute second trigger
AccTriggerHelper.secondMethod(Trigger.new);
// both of these trigger will follow the execution order
}
Else if(Trigger.isUpdate)
{
// write the code for before update
}
Else if(Trigger.isDelete)
{
// write the code for before delete
}
Else if(Trigger.isUndelete)
{
// write the code for before undelete
}
}
Else if(Trigger.isAfter)
{
If(Trigger.isInsert)
{
// write the code for after insert
}
Else if(Trigger.isUpdate)
{
// write the code for after update
}
Else if(Trigger.isDelete)
{
// write the code for after delete
}
Else if(Trigger.isUndelete)
{
// write the code for after undelete
}
}
}
Handling Recursion in Triggers:
Recursion in Triggers happens when a Trigger is called repeatedly, resulting in an infinite loop.
To counter recursion, we need to:
- Create another class called RecursiveTriggerHandler.
- Make use of “Static” variables.
Example:
public class RecursiveTriggerHandler {
public static Boolean isFirstRun = true;
}
trigger BranchTrigger on Branch__c (before update) {
if(RecursiveTriggerHandler.isFirstRun){
RecursiveTriggerHandler.isFirstRun = false;
// Call Helper Class method
BranchTriggerHelper.firstMethod(Trigger.new);
}
}
The first instance of the Trigger is run and if this variable is true, the logic in the Helper Class executes.
Other Best Practices for writing triggers:
- Always create only one Trigger per object.
- Create logic-less Triggers and use Helper Class Design Pattern in which the helper class will contain all the logic.
- Create context-specific handler methods in the Helper Class.
- Bifurcate “insert” and “update” Trigger logic contexts and create two different methods in the Trigger’s helper class.
Example:
trigger PositonTrigger on Position__c (after insert, after update) {
if(Trigger.isAfter && Trigger.isInsert) { PositionTriggerHandler.handleAfterInsert(Trigger.new);
} else if(Trigger.isAfter && Trigger.isUpdate) { PositionTriggerHandler.handleAfterUpdate(Trigger.new, Trigger.old);
}
}