Hot Posts

6/recent/ticker-posts

Apex Triggers in salesforce

Introduction to apex programming

 What are Triggers in Salesforce?

An Apex trigger is a script that runs either before or after a data manipulation language (DML) event takes place. These triggers allow you to perform custom actions on Salesforce records, such as inserting, updating, or deleting them. Similar to how database systems support triggers, Apex provides trigger support for managing records.

Types of Apex Triggers

Different Types of Apex Triggers:

1. Before Triggers: Before-triggers are utilized to update, modify, or validate records before they are saved to the database.

2. After-Triggers: After-triggers are used to access field values that are set by the system, such as recordId and lastModifiedDate. Additionally, we can make changes to other records within the After-trigger. However, it is important to note that we cannot modify the record that initiated or triggered the execution of the After-trigger, as these records are read-only.

What is Trigger Syntax?

  1. trigger TriggerName on ObjectName(trigger_events) { 
  2. //code-block  
  3. } 

Trigger events in salesforce?

A trigger is a set of statement which can be executed on the following events. In above trigger events one or more of below events can be used with comma-separated.

Here is a list of trigger events in salesforce

  • before insert
  • before update
  • before delete
  • after insert
  • after update
  • after delete
  • after undelete

When to use salesforce triggers

We should use triggers to perform tasks that can’t be done by using the point-and-click tools in the Salesforce user interface. For example, if validating a field value or updating a field on a record, use validation rules and workflow rules instead.

  1. trigger FirstTrigger on Account(before insert) {  
  2. System.debug(‘I am before insert.’); 
  3. } 
  4. Trigger SecondTrigger on Account(after insert) { 
  5. System.debug(‘I am after insert.’); 
  6. } 

Note:

If you update or delete a record in its before trigger, or delete a record in its after trigger you’ll receive an error and this includes both direct and indirect operations.

Trigger Order of Execution In Apex

In Salesforce, there is a specific sequence in which events are executed whenever a record is changed or inserted. The trigger execution follows the following steps:

1. Load the original record or initialize on insert.

2. Replace the old record values with the new values.

3. Execute all before triggers.

4. Run the system and user-defined validation rules.

5. Save the record without committing it to the database.

6. Execute all after triggers.

7. Execute the assignment rules.

8. Execute the auto-response rules.

9. Execute the workflow rules.

10. If there are workflow field updates, execute the field update.

11. If the record was updated with a workflow field update, execute before and after triggers created on the object in the context again, but only once.

12. Execute the processes and flows on that record.

13. Execute the escalation rules.

14. Update the roll-up summary fields and cross-object formula fields.

15. Repeat the same process with the affected parent or grandparent records.

16. Evaluate criteria-based sharing rules.

17. Commit all DML operations to the database.

18. Execute post-commit logic, such as sending emails.

What are the considerations while implementing the Triggers?

Before implementing the triggers, it is important to consider the following points:

1. The upsert trigger is designed to fire on four different events: before insert, before update, after insert, and after update.

2. On the other hand, merge triggers are only fired on delete events.

3. Field history is updated after the trigger has successfully processed the data.

4. To ensure efficient processing, any callout made by the trigger should be asynchronous. This allows the trigger to continue execution without waiting for the response.

5. It is worth noting that a trigger cannot include the static keyword in its code.

6. If a trigger completes successfully, any changes made are committed to the database. However, if the trigger fails, the transaction is rolled back.

What are context variables in triggers?

All triggers define implicit variables that allow developers to access run-time context. These variables are contained in the System.Trigger class.

Here is list of context variables in triggers

  1. isExecuting: Returns true if the current context for the Apex code is a trigger, not a Visualforce page, a Web service, or an executeanonymous() API call.
  2. isInsert: Returns true if this trigger was fired due to an insert operation, from the Salesforce user interface, Apex, or the API.
  3. isUpdate: Returns true if this trigger was fired due to an update operation, from the Salesforce user interface, Apex, or the API.
  4. isDelete: Returns true if this trigger was fired due to a delete operation, from the Salesforce user interface, Apex, or the API.
  5. isBefore: Returns true if this trigger was fired before any record was saved.
  6. isAfter: Returns true if this trigger was fired after all records were saved.
  7. isUndelete: Returns true if this trigger was fired after a record is recovered from the Recycle Bin (that is, after an undelete operation from the Salesforce user interface, Apex, or the API.)
  8. new: Returns a list of the new versions of the sObject records. This sObject list is only available in insert, update, and undelete triggers, and the records can only be modified in before triggers.
  9. newMap: A map of IDs to the new versions of the sObject records. This map is only available in before update, after insert, after update, and after undelete triggers.
  10. old : Returns a list of the old versions of the sObject records. This sObject list is only available in update and delete triggers.
  11. oldMap: A map of IDs to the old versions of the sObject records. This map is only available in update and delete triggers.
  12. size: The total number of records in a trigger invocation, both old and new.

Types of Trigger Context Variables

  1. Trigger.new: This variable returns a list of the new version of sObject records.
  2. Trigger.old: This variable returns a list of the old version of sObject records.
  3. Trigger.newMap: This variable returns a map of ids to the new versions of sObject records.
  4. Trigger.oldMap: A map of IDs to the old version of the sObject records.

Considerations Of Context Variables

Points to consider with Trigger Context Variables:

  1. Trigger.new and Trigger.old cannot be used in Apex DML Operations.
  2. You can use an sObject to change its own field values using a trigger.new but only in before triggers.
  3. Trigger.old is always read-only.
  4. You can not delete Trigger.new.
  5. Upsert and merge events do not have their own triggers, instead, they fire other triggers based on the events as a result of the merge operation.

Trigger.new

This variable returns a list of the new version of sObject records. This list is only available in insert, update, and undelete triggers.

  1. trigger ApexTrigger on Account(before insert) {  
  2. for (Account acc: Trigger.new) { 
  3. acc.NumberOfEmployees = 100; 
  4. } 
  5. } 

Trigger.old

This variable returns a list of the old version of sObject records. This list is only available in update and delete triggers

  1. trigger ApexTrigger on Opportunity(before update) {  
  2. // Only available in Update and Delete Triggers for (Opportunity oldOpp: Trigger.old) { 
  3. for (Opportunity newOpp: Trigger.new) { 
  4. if (oldOpp.Id == newOpp.Id && oldOpp.Amount != newOpp.Amount) newOpp.Amount.addError('Amount cannot be changed');  
  5. // Trigger Exception  
  6. }  
  7. }  
  8. } 

Trigger.newMap

This variable returns a map of ids to the new versions of sObject records.

Note:This map is only available in before update, after insert, after update and after undelete triggers.

  1. trigger ApexTrigger on Account(before update) {  
  2. // Only available in beforeUpdate, afterUpdate, afterInsert, afterUndelete Triggers  
  3. Map < Id, Account > nMap = new Map < Id, Account > ();  
  4. nMap = Trigger.newMap;  
  5. List < Contact > cList = [SELECT LastName FROM Contact WHERE AccountId IN: nMap.keySet()]; 
  6. for (Contact c: cList) { 
  7. Account a = nMap.get(c.AccountId); c.MailingCity = a.BillingState; 
  8. }  
  9. update cList;  
  10. } 

Trigger.oldMap

A map of IDs to the old version of the sObject records.

  1. trigger ApexTrigger on Opportunity(before update) { 
  2. // Only available in Update and Delete Triggers  
  3. Map < Id, Opportunity > oMap = new Map < Id, Opportunity > (); 
  4. oMap = Trigger.oldMap; 
  5. for (Opportunity newOpp: trigger.new) { 
  6. Opportunity oldOpp = new Opportunity(); 
  7. oldOpp = oMap.get(newOpp.Id); 
  8. if (newOpp.Amount != oldOpp.Amount) { 
  9. newOpp.Amount.addError('Amount cannot be changed'); // Trigger Exception }  
  10. } 
  11. } 

Note:This map is only available in update & delete triggers.

Trigger Exceptions

To prevent DML operations, triggers can utilize the addError() method on a record or field. This method can be applied to trigger.new records in insert and update triggers, as well as Trigger.old records in delete triggers. By doing so, a custom error message will be exhibited in the application interface and logged into the system.

  1. Trigger AccountDeletion on Account(before delete) { 
  2. for (Account a: [SELECT id FROM Account where id in (SELECT AccountId from opportunities) AND id in : Trigger.old]) {  
  3. Trigger.oldMap.get(a.id).addError(‘Cannot delete account with related opportunities.’); 
  4. }  
  5. } 

Note:

If a trigger even throws an unhandled exception, all records are marked with an error & no further processing takes place.

What is a recursive Trigger?

Recursion is the process of executing the same Trigger multiple times to update the record again and again due to automation. There may be chances we might hit the Salesforce Governor Limit due to Recursive Trigger.

avoid recursion in trigger?

There are different ways to solve the recursion in the trigger.

  1. Use Static Boolean Variable
  2. Use Static Set to Store Record Id.
  3. Use Static Map
  4. Use Old Map
  5. Follow Best practice for triggers

Let’s take a look in detail at all solutions and their limitations.

1. Use Static Boolean Variable

You can create a class with a static Boolean variable with a default value of true. In the trigger, before executing your code keep a check that the variable is true or not. Once you check make the variable false.

Apex Class with Static Variable

  1. public class ContactTriggerHandler{  
  2. public static Boolean isFirstTime = true; 
  3. } 

Trigger Code

  1. Trigger ContactTriggers on Contact (after update){  
  2. Set<String> accIdSet = new Set<String>();  
  3. if(ContactTriggerHandler.isFirstTime){ 
  4. ContactTriggerHandler.isFirstTime = false; System.debug('---- Trigger run ---->'+Trigger.New.size() ); for(Contact conObj : Trigger.New){  
  5. if(conObj.name != 'Test') { accIdSet.add(conObj.accountId);  
  6. }  
  7. }  
  8. // any code here  
  9. }  
  10. } 

This is good for less than 200 records. But what about if we have a large set of records it won’t work. It will update the first 200+ records then it will skip the others. Let’s execute the above code with more than 200+ records.

  1. List<Contact> listCont =[Select id, firstname from contact limit 400]; 
  2. update listCont; 
  3. System.debug('-listCont.size--->'+listCont.size()); 

In my org, we have 327 contacts. Let’s query them all and update them. In this case, the trigger should execute 2 times. But due to static variables trigger will only execute once and will skip the remaining record from processing.

Check Salesforce document for Apex Trigger Best practices to avoid recursion by using Static Boolean variable in apex class. Static Boolean is an anti-pattern so please DO NOT USE.

Use Static Set or Map

So It is better to use a Static set or map to store all executed record ids. So when next time it will execute again we can check record is already executed or not. Let modify the above code with the help of Set.

  1. public class ContactTriggerHandler{ 
  2. public static Set<Id> setExecutedRecord = new Set<Id>(); 
  3. } 

Let us the same set variable in Trigger.

  1. trigger ContactTriggers on Contact (after update){  
  2. System.debug('---- Trigger run ---->'+Trigger.New.size() );  
  3. for(Contact conObj : Trigger.New){ if(ContactTriggerHandler.setExecutedRecord.contains(conObj.id)){ if(conObj.name != 'Test') {  
  4. // logic  
  5. }  
  6. }  
  7. }  
  8. // any code here 
  9. } 

Let’s test the above code with more than 200 records. Execute the same script and see the result.

The use of a static Set is less than ideal because there are plenty of times when you may get both an insert and an update within the same transaction (esp. with a mix of the apex and flow logic).

3. Use Static Map

You can also use Static map to store the event name and set of Id like below.

  1. public static Map<String,Set<Id>> mapExecutedRecord = new Map<String,Set<Id>>; 

Use it like below

  1. if(!ContactTriggerHandler.mapExecutedRecord.containsKey('afterUpdate')){ 
  2. ContactTriggerHandler.mapExecutedRecord.put('afterUpdate',new Set<ID>()); 
  3. } 
  4. .... for(){ if(ContactTriggerHandler.mapExecutedRecord.get('afterUpdate').contains(conObj.id)) { } } 

In this way you can handle all event using same map.

4. Use Old Map

Always use Old map to compare the values before executing the trigger logic. Here is example how we can compare old value to resolve the recursion in Trigger.

  1. for(Opportunity opp:Trigger.New){ 
  2. if(opp.Stage != Trigger.oldMap.get(opp.Id).Stage){ 
  3. //Your logic 
  4. } 
  5. } 

In case of OldMap your code will only execute once value will change.

5. Follow Best practice for triggers

Follow Trigger best practices and use the Trigger framework to resolve the recursion.

  1. One trigger per object so you don’t have to think about the execution order as there is no control over which trigger would be executed first.
  2. Logic-less Triggers – use Helper classes to handle logic.
  3. Code coverage 100%
  4. Handle recursion – To avoid the recursion on a trigger, make sure your trigger is getting executed only one time. You may encounter the error : ‘Maximum trigger depth exceeded’, if recursion is not handled well.

What do you mean by the bulkifying trigger?

Bulkifying triggers means writing apex triggers using a bulk design pattern so that triggers have better performance and consume fewer server resources. As a result of it, bulkified code can process a large number of records efficiently and run within governor limits on the Overview platform.

Main Characteristics Of Bulkified Trigger

  1. Operating on all records of the trigger.
  2. Performing SOQL & DML on collections of sObjects instead of single sObject at a time.
  3. Using maps to hold query results organized by record id. Avoid query within a query and save records in the map which later can be accessed through a map rather than using SOQL.
  4. Using Sets to isolate distinct records.

Refeeral link:Apex Triggers

Bulkified Triggers – Coding Practice Examples

Example 1:

Avoid creating Triggers that work only for individual records but not for entire datasets:

  1. trigger testTrigger on Acount__c(before insert) { 
  2. Acount__c acc = Trigger.New[0]; 
  3. acc.Address__c = 'Temporary Address'; 
  4. } 

Create Triggers that use loops to help iterate over a list of records within a transaction:

  1. trigger testTrigger on Acount__c(before insert) { 
  2. integer i = 1; 
  3. for (Acount__c acc: Trigger.new) { 
  4. acc.Address__c = 'Test Address ' + i; 
  5. i++; 
  6. } 
  7. } 

Example 2:

In this code, let’s assume 200 Account records are updated, so the “for” loop would iterate over 200 records.

  1. trigger BranchTrigger on Branch__c(before update) { 
  2. for (Branch__c br: Trigger.new) { 
  3. List < Acount__c > accList = [SELECT Name, Account_Name__c, Address__c, Balance__c FROM Acount__c 
  4. WHERE Acount_of_Branch__c = : br.id 
  5. ]; 
  6. System.debug(accList); 
  7. // Perform specified logic with queried records  
  8. } 
  9. } 

Now let’s look at a good example of querying bulk data and iterating it.

  1. trigger BranchTrigger on Branch__c(before update) { 
  2. List < Acount__c > accList = [SELECT Name, Account_Name__c, Address__c, Balance__c FROM Acount__c 
  3. WHERE Acount_of_Branch__c IN: Trigger.New 
  4. ]; 
  5. System.debug(accList); 
  6. for (Acount__c acc: accList) { 
  7. // Perform specified logic with queried records  
  8. } 
  9. } 

Example 3:

DML statements are also bound by Governor Limits; you can call only 150 DML operations in a transaction.

  1. trigger BranchTrigger on Branch__c(before update) { 
  2. List < Acount__c > accList = [SELECT Name, Account_Name__c, Address__c, Balance__c FROM Acount__c 
  3. WHERE Acount_of_Branch__c IN: Trigger.New 
  4. ]; 
  5. System.debug(accList); 
  6. integer i = 0; 
  7. for (Acount__c acc: accList) { 
  8. acc.Address__c = 'Test Address ' + i; 
  9. i++; 
  10. update acc; 
  11. } 
  12. } 

Let’s look a correct coding example where we have instantiated another Account object list called “accToUpdate.”

  1. trigger BranchTrigger on Branch__c(before update) { 
  2. List < Acount__c > accToUpdate = new List < Acount__c > (); 
  3. List < Acount__c > accList = [SELECT Name, Account_Name__c, Address__c, Balance__c FROM Acount__c 
  4. WHERE Acount_of_Branch__c IN: Trigger.New 
  5. ]; 
  6. System.debug(accList); 
  7. integer i = 0; 
  8. for (Acount__c acc: accList) { 
  9. acc.Address__c = 'Test Address ' + i; 
  10. i++; 
  11. accToUpdate.add(acc); 
  12. } 
  13. if (!accToUpdate.isEmpty()) { 
  14. update accToUpdate; 
  15. } 
  16. } 

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:

  1. trigger accUpdate on Account(before insert, after insert, before update, after update) { 
  2. if (Trigger.isBefore) { 
  3. If(Trigger.isInsert) { 
  4. // execute first trigger  
  5. AccTriggerHelper.firstMethod(Trigger.new); 
  6. // execute second trigger  
  7. AccTriggerHelper.secondMethod(Trigger.new); 
  8. // both of these trigger will follow the execution order  
  9. } 
  10. Else 
  11. if (Trigger.isUpdate) { 
  12. // write the code for before update  
  13. } 
  14. Else 
  15. if (Trigger.isDelete) { 
  16. // write the code for before delete  
  17. } 
  18. Else 
  19. if (Trigger.isUndelete) { 
  20. // write the code for before undelete  
  21. } 
  22. } 
  23. Else 
  24. if (Trigger.isAfter) { 
  25. If(Trigger.isInsert) { 
  26. // write the code for after insert  
  27. } 
  28. Else 
  29. if (Trigger.isUpdate) { 
  30. // write the code for after update  
  31. } 
  32. Else 
  33. if (Trigger.isDelete) { 
  34. // write the code for after delete  
  35. } 
  36. Else 
  37. if (Trigger.isUndelete) { 
  38. // write the code for after undelete  
  39. } 
  40. } 
  41. } 

Let’s understand the concept of the Trigger Helper Class Pattern with an example of a Trigger on Account object, which fires for all the Trigger events.

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:

  1. Create another class called RecursiveTriggerHandler.
  2. Make use of “Static” variables.

Example:

  1. trigger BranchTrigger on Branch__c(before update) { 
  2. if (RecursiveTriggerHandler.isFirstRun) { 
  3. RecursiveTriggerHandler.isFirstRun = false; 
  4. // Call Helper Class method  
  5. BranchTriggerHelper.firstMethod(Trigger.new); 
  6. } 

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:

  1. Always create only one Trigger per object.
  2. Create logic-less Triggers and use Helper Class Design Pattern in which the helper class will contain all the logic.
  3. Create context-specific handler methods in the Helper Class.
  4. Bifurcate “insert” and “update” Trigger logic contexts and create two different methods in the Trigger’s helper class.

Example:

  1. trigger PositonTrigger on Position__c(after insert, after update) { 
  2. if (Trigger.isAfter && Trigger.isInsert) { 
  3. PositionTriggerHandler.handleAfterInsert(Trigger.new); 
  4. } else if (Trigger.isAfter && Trigger.isUpdate) { 
  5. PositionTriggerHandler.handleAfterUpdate(Trigger.new, Trigger.old); 
  6. } 
  7. } 

Some important points about triggers:

  • The order of execution isn’t guaranteed when having multiple triggers for the same object with the same events.
  • When a DML call is made with “Partial Success allowed” then more than one attempt can be made to save the successful records if the initial attempts result in errors for the same records.
  • Some operations do not invoke triggers:Cascade Delete

Records that don’t initiate a delete event don’t cost trigger execution.

  • Cascade update of child records that are re-parented as a result of merge operation.
  • Mass campaign status changes.
  • Update account triggers do not fire before and after a business account record type is changed to a person account record type or vice versa.
  • Before trigger associated with the following operations are fired during lead conversion only if validations and triggers for lead conversion are enabled in the organizationInsert an account, contact & opportunityUpdate of account and contacts.
  • Fields not available in before insert triggersOpportunity.AmountOpportunity.isWonOpportunity.isClosedID (for all records)CreatedDate and LastModifiedDate
  • Fields not editable in after triggersEvent.WhoIDTask.WhoID
  • When an opportunity has no line items then the amount can be modified by a before trigger.

What Is Recursion In Triggers?

Recursion in Triggers happens when a Trigger is called repeatedly, resulting in an infinite loop.

To counter recursion, we need to:

  1. Create another class called RecursiveTriggerHandler.
  2. Make use of “Static” variables.

Example:

  1. trigger BranchTrigger on Branch__c(before update) { 
  2. if (RecursiveTriggerHandler.isFirstRun) { 
  3. RecursiveTriggerHandler.isFirstRun = false; 
  4. // Call Helper Class method  
  5. BranchTriggerHelper.firstMethod(Trigger.new); 
  6. } 

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:

  1. Always create only one Trigger per object.
  2. Create logic-less Triggers and use Helper Class Design Pattern in which the helper class will contain all the logic.
  3. Create context-specific handler methods in the Helper Class.
  4. Bifurcate “insert” and “update” Trigger logic contexts and create two different methods in the Trigger’s helper class.
  1. trigger PositonTrigger on Position__c(after insert, after update) { 
  2. if (Trigger.isAfter && Trigger.isInsert) { 
  3. PositionTriggerHandler.handleAfterInsert(Trigger.new); 
  4. } else if (Trigger.isAfter && Trigger.isUpdate) { 
  5. PositionTriggerHandler.handleAfterUpdate(Trigger.new, Trigger.old); 
  6. } 
  7. } 

Post a Comment

0 Comments