0% found this document useful (0 votes)
68 views

Mastering Triggers for Interviews

Uploaded by

Cnu Vas
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
68 views

Mastering Triggers for Interviews

Uploaded by

Cnu Vas
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 36

Mastering Triggers for Interviews

Trigger Execution Scenarios


● Insert: Before and After
● Update: Before and After
● Delete: Before and After
● Undelete: After

Trigger Context Variables

Trigger.isBefore - Boolean

Trigger.isAfter - Boolean

Trigger.isInsert - Boolean

Trigger.isUpdate - Boolean

Trigger.isDelete - Boolean

Trigger.IsUndelete - Boolean

Trigger.new - List

Trigger.Old - List

Trigger.newMap - Map

Trigger.OldMap- Map

Trigger.OperationType -Enum

Trigger.Size - Integer

Trigger.isExecuting - Boolean

Trigger Methods to Keep in Mind


1. Validation
2. Roll-up Summary
3. Before Insert
4. After Insert
5. Before Update
6. After Update
7. Before Delete
8. After Delete
9. Recursive Handler
10. Undelete: If Parent is Undeleted, Child is also Restored
Event vs Context Variable

EVENT BEFORE AFTER

INSERT Trigger.New Trigger.New

Trigger.Newmap
UPDATE Trigger.New Trigger.New
Trigger.Newmap Trigger.Newmap
Trigger.Old Trigger.Old
Trigger.OldMap Trigger.OldMap

DELETE Trigger.Old Trigger.Old


Trigger.OldMap Trigger.OldMap

UNDELETE Trigger.New
Trigger.Newmap

How to Decide Which Event to Choose

Action to Perform Event DML Required

Before After

Field Update on the Y


same record

Add Validation error Y Y

Field Update on the Y Y


related record

Create New Y Y
Record(Related/Non
Related)

Sending Y
Email/Custom
Notification

Call out to external


system
Flow vs. Apex Trigger

When to Use Flow:

1. Simple Automation: For straightforward processes, like sending notifications,


updating fields, or creating records based on specific criteria.
2. User Interaction: When you need a screen-based interaction, such as a form for
users to fill out.
3. Declarative Solution: When the task can be accomplished using Salesforce’s
declarative tools, making it easier to maintain and less technical.
4. Low Volume Updates: For single or low-volume record updates, where bulk
processing isn’t a requirement.

Example: Use Flow to automatically send a welcome email to a new Contact after
they’re created.

When to Use Trigger:

1. Complex Logic: When you need advanced logic that flows can’t handle, such as
multiple conditional checks, loops, or calculations across related objects.
2. Bulk Processing: For handling bulk updates or inserts, where large amounts of
records need processing at once (e.g., data migrations or mass updates).
3. Cross-Object Actions: When you need to update records on unrelated objects
or perform actions on objects not directly related to each other.
4. Real-Time Processing: For faster, real-time processing where performance is
critical.

Example: Use a Trigger to automatically update an Account’s Last_Purchase_Date


whenever an Opportunity related to that Account is closed as "Won."

Points to Keep in Mind


Trigger Chunk Size
● Triggers process records in chunks of 200.
● Example: If 210 records are inserted, the trigger runs twice—processing the first 200
records, then the remaining 10

Avoiding Recursive Triggers


Recursive triggers are tricky—they can lead to max trigger depth errors due to repeated
execution.
Prevent Recursion Using:

1. Static Variables
2. Static Set of IDs (better for handling more than 200 records)

Static Variables vs. Static Sets in Triggers


When trying to prevent recursive triggers, many developers use static variables. However,
using static variables alone can lead to missed records in scenarios where you’re processing
more than 200 records.

Here's Why:

● With a static variable, only the first 200 records are processed.
● The additional records (like the remaining 10 in our example) won’t be processed, and no
error will be thrown, which can lead to incomplete processing!

Solution: Use a Static Set of IDs

Using a Static Set of IDs ensures that:

1. All records in chunks are processed, preventing any missed actions.


2. Remaining records after the first 200 are handled without the risk of unprocessed data.

Test-Driven Development (TDD)


● Use TDD to validate bulkified records!
● Create 500 test records and watch as they’re deleted after testing by default.

Common Errors

● Maximum Trigger Depth Exceeded: Occurs if recursion isn’t controlled.


○ Example: If “Customer Priority” is set to High, and you set “Rating” to Hot in the
same After Trigger—expect a recursion error!

Before vs. After Triggers


● Before Trigger: Great for validations, updating the same record, or adding errors before
DML operations.
○ Examples:
■ Validate data on insert/update
■ Prevent delete if criteria aren’t met
● After Trigger: Ideal for creating related records, sending emails, or logging activities
where context variables are read-only.
○ Examples:
■ Create a Contact when an Account is created
■ Send an email when an Opportunity is created

Roll-Up Summaries for Lookups


1. Roll up counts, sums, averages, or other aggregates to the parent.
2. Aggregate child records and update parent values for insights.
Note: Post-undeletion automation isn’t supported in Flows.

Additional Notes

● System Validation: Fields like Name, Mobile, Email have character limits.
● Synchronous Execution: Triggers process records in a batch size of 200.
● External Callouts: Use Future methods for callouts as triggers are synchronous.

Best Practice
1. One Trigger per Object (Control execution order)
2. Implement business logic in a Trigger Handler
3. Always bulkify for single/multiple records
4. Avoid nested loops ➡Use Maps instead
5. Control recursion carefully
6. Document code thoroughly
7. Before deployment, remove unused code and debug statements
8. Unit Test with bulk scenarios using test classes
9. Master Trigger Context Variables (like Trigger.New, Trigger.Old, etc.)

Adding Try-Catch for Error Handling


Using a try-catch block in the Trigger Handler methods can help catch unexpected errors, log
them for troubleshooting, and ensure the code doesn’t fail silently

Here's a more structured version of the syntax and example code for a Trigger
Handler in Salesforce, with comments to guide through each part:

Apex Trigger Handler Pattern


The Trigger Handler pattern helps in organizing the trigger code efficiently, allowing for clean
and manageable logic. Below is the syntax to create a handler class and a trigger on the Account
object.

Apex Trigger Handler Class


public class AccountTriggerHandler {

// Declare variables for the current and old records List<Account> triggerNew;
List<Account> triggerOld; Map<Id,
Account> triggerNewMap; Map<Id,
Account> triggerOldMap;

// Constructor to initialize variables with trigger context public


AccountTriggerHandler() {
triggerNew = (List<Account>) Trigger.New; triggerOld =
(List<Account>) Trigger.Old; triggerNewMap = (Map<Id, Account>)
Trigger.NewMap;
triggerOldMap = (Map<Id, Account>) Trigger.OldMap;
}

// Main method to handle actions based on the trigger operation type public void doAction() {
// Switch statement for handling different trigger operations switch on
Trigger.operationType {
when BEFORE_INSERT {
onBeforeInsert();
}

when AFTER_INSERT {
onAfterInsert();
}
when BEFORE_UPDATE {
onBeforeUpdate();
}
when AFTER_UPDATE {
onAfterUpdate();
}
when BEFORE_DELETE {
onBeforeDelete();
}
when AFTER_DELETE {
onAfterDelete();
}
when AFTER_UNDELETE {
onAfterUndelete();
}
}
}

// Define methods for each trigger event as needed public void


onBeforeInsert() {
// Logic for before insert
}

public void onAfterInsert() {


// Logic for after insert
}

public void onBeforeUpdate() {


// Logic for before update
}
public void onAfterUpdate() {
// Logic for after update
}
public void onBeforeDelete() {
// Logic for before delete
}

public void onAfterDelete() {


// Logic for after delete
}

public void onAfterUndelete() {


// Logic for after undelete
}
}

This trigger references the AccountTriggerHandler class to execute actions based on the
specified events.

Apex Trigger
trigger AccountTrigger on Account (before insert, after insert, before update,
after update, before delete, after delete, after undelete) {

// Initialize the handler and call the action method AccountTriggerHandler


handler = new AccountTriggerHandler();
handler.doAction();
}

Notes on Trigger Handler Pattern

● Benefits: This pattern keeps your code modular, making it easier to maintain and scale. It
also allows for better error handling and improved readability.
● Bulkification: Ensure that each method inside the handler class processes records in bulk
to prevent hitting governor limits.
● Trigger Operations: Use Trigger.operationType to determine the context in which the
trigger is running (insert, update, delete, etc.).
● Testing: For each method in the handler, write comprehensive test classes covering
different scenarios to ensure that your triggers run smoothly with bulk data.
Examples For Each Method
Before Insert
Scenario: Set rating hot when account created with industry as banking.

Apex - Trigger handler


public class AccountTriggerHandler {

// Declare the Variables List<Account>


triggerNew; List<Account> triggerOld;
Map<Id, Account> triggerNewMap;
Map<Id, Account> triggerOldMap;

// Constructor to initialize the variables public


AccountTriggerHandler() {
triggerNew = (List<Account>) Trigger.New; triggerOld =
(List<Account>) Trigger.Old; triggerNewMap = (Map<Id, Account>)
Trigger.NewMap; triggerOldMap = (Map<Id, Account>)
Trigger.OldMap;
}

// Action based on trigger operation type public void


doAction()
{
// Switch statement to handle different trigger operations Switch on
Trigger.operationType
{
When BEFORE_INSERT
{
onBeforeInsert();
}
}
}
// Logic for before insert operation public void
onBeforeInsert() {
updateRating();
}
// Method to update the rating to 'Hot' if the account industry is 'Banking'
public void updateRating() {
for (Account record : triggerNew) {
if (record.Industry == 'Banking') { record.Rating = 'Hot';
}
}
}
}

Apex Trigger
trigger AccountTrigger on Account (before insert) { AccountTriggerHandler handler = new
AccountTriggerHandler(); handler.doAction();
}

Here are the best practices followed in the above code, each explained in a single line:

Use of Switch: The Switch on Trigger.operationType structure is used to determine which


operation the trigger is running on. In this case, BEFORE_UPDATE is handled with the
onBeforeUpdate() method.

1. Bulkification: The code is designed to handle multiple records efficiently in a single


transaction.
2. Trigger Handler Pattern: Business logic is moved to a separate handler class for cleaner
separation and maintainability.
3. Minimal Logic in Trigger: The trigger only delegates work to the handler class, keeping
the trigger lightweight.
4. Switch-Based Control Flow: The use of the Switch makes it easy to add more
operations (like BEFORE_INSERT, AFTER_UPDATE, etc.) in the future, enhancing
code flexibility and readability.
5. Proper Constructor: The handler class has a correctly defined constructor for variable
initialization.
6. Reusability: Each operation (e.g., updateRating()) is in a separate method, making it easy
to extend or modify.
7. Null Checks Avoided: Fields are directly accessed as the conditions ensure valid entries,
improving code efficiency.
Before Update

Scenario:When a user creates an account record without rating then


prepopulates the rating as cold.

Apex Trigger Handler


public class AccountTriggerHandler {

// Declare the Variables List<Account>


triggerNew; List<Account> triggerOld;
Map<Id, Account> triggerNewMap;
Map<Id, Account> triggerOldMap;

// Constructor to initialize the variables public


AccountTriggerHandler() {
triggerNew = (List<Account>) Trigger.New; triggerOld =
(List<Account>) Trigger.Old; triggerNewMap = (Map<Id, Account>)
Trigger.NewMap; triggerOldMap = (Map<Id, Account>)
Trigger.OldMap;
}

// Action based on Trigger Operation Type public void


doAction() {
// Switch statement to handle different trigger operations Switch on
Trigger.operationType {
when BEFORE_UPDATE {
onBeforeUpdate();
}
}
}

// Logic for Before Update Operation public void


onBeforeUpdate() {
updateRating();
}
// Method to update the Rating as 'Cold' if the Rating field is empty
public void updateRating() {
for (Account record : triggerNew) { if
(record.Rating == null) {
record.Rating = 'Cold';
}
}
}
}
Trigger
trigger AccountTrigger on Account (before update) { AccountTriggerHandler handler = new
AccountTriggerHandler(); handler.doAction();
}

Before Delete
Scenario: Leads with a status of 'Working Contacted' should not be deleted
under any circumstances.

Apex Trigger Handler


public class LeadTriggerHandler {

// Declare the Variables List<Lead>


triggerOld; Map<Id, Lead>
triggerOldMap;

// Constructor to initialize the variables public


LeadTriggerHandler() {
triggerOld = (List<Lead>) Trigger.Old; triggerOldMap = (Map<Id,
Lead>) Trigger.OldMap;
}

// Action based on trigger operation type public void


doAction() {

// Switch statement to handle trigger operation types Switch on


Trigger.operationType {
when BEFORE_DELETE {
onBeforeDelete();
}

}
}

// Logic for Before Delete Operation public void


onBeforeDelete() {
beforeDeleteValidation();
}
// Method to prevent deletion of leads with 'Working Contacted' status
public void beforeDeleteValidation() { for (Lead record
: triggerOld) {
if (record.Status == 'Working - Contacted') { record.addError('Lead cannot be
deleted when the
status is Working Contacted.');
}
}
}
}

Trigger
trigger LeadTrigger on Lead (before delete) { LeadTriggerHandler handler = new
LeadTriggerHandler(); handler.doAction();
}

After Insert
Scenario: When a Opportunity Created Create a Task for Follow up

Apex Trigger Handler


public class OpportunityHandler { List<Opportunity>
triggerNew; List<Opportunity> triggerOld;
Map<Id, Opportunity> triggerNewMap;
Map<Id, Opportunity> triggerOldMap;

// Constructor to initialize trigger context variables public


OpportunityHandler() {
this.triggerNew = (List<Opportunity>) Trigger.New; this.triggerOld =
(List<Opportunity>) Trigger.Old; this.triggerNewMap = (Map<Id, Opportunity>)
Trigger.NewMap; this.triggerOldMap = (Map<Id, Opportunity>) Trigger.OldMap;
}

// Main action handler public void


doAction() {
switch on Trigger.operationType { when
AFTER_INSERT {
onAfterInsert();
}

}
}

// Method to create follow-up tasks after an Opportunity is inserted


public void onAfterInsert() {
List<Task> taskList = new List<Task>();
// Query the Opportunities inserted
List<Opportunity> opportunityList = [SELECT Id, StageName, OwnerId FROM
Opportunity WHERE Id IN :triggerNew];

// Loop through each Opportunity and create a Task for follow-up for (Opportunity
record : opportunityList) {
Task taskRecord = new Task();
taskRecord.WhatId = record.Id;
taskRecord.OwnerId = record.OwnerId; // Owner of the
Opportunity
taskRecord.Subject = 'Follow up';
taskList.add(taskRecord);
}

// Insert the Task list with error handling try {


insert taskList;
} catch (DmlException e) {
// Log the exception or handle it as needed System.debug('Error while creating
tasks: ' +
e.getMessage());
}
}
}

Trigger
trigger OpportunityTrigger on Opportunity (after insert, before update, after update) {
OpportunityHandler obj = new OpportunityHandler(); obj.doAction();
}
After Update
Scenario: When an account is marked as inactive then close all the cases if any.

Apex Trigger Handler


public class AccountHandler { List<Account>
triggerNew; List<Account> triggerOld;
Map<Id, Account> triggerNewMap;
Map<Id, Account> triggerOldMap;

// Constructor to initialize trigger context variables public


AccountHandler() {
this.triggerNew = (List<Account>) Trigger.New; this.triggerOld = (List<Account>)
Trigger.Old; this.triggerNewMap = (Map<Id, Account>) Trigger.NewMap;
this.triggerOldMap = (Map<Id, Account>) Trigger.OldMap;
}

// Main action handler public void


doAction() {
switch on Trigger.operationType { when
AFTER_UPDATE {
onAfterUpdate();
}
}
}
// Method to close all cases when an account is marked as inactive
public void onAfterUpdate() {
Set<Id> inactiveAccountIds = new Set<Id>();

// Collect all account IDs where Active c changed to 'No' for


(Account acc : triggerNew) {
Account oldAcc = triggerOldMap.get(acc.Id);
if (oldAcc.Active c != 'No' && acc.Active c == 'No') {
inactiveAccountIds.add(acc.Id);
}
}
// Query all open cases related to the inactive accounts List<Case> caseList =
new List<Case>();
if (!inactiveAccountIds.isEmpty()) {
List<Case> relatedCases = [SELECT Id, Status, AccountId
FROM Case
WHERE
AccountId IN :inactiveAccountIds
AND Status != 'Closed'];
// Set each case status to 'Closed'
for (Case caseRecord : relatedCases) { caseRecord.Status = 'Closed';
caseList.add(caseRecord);
}
}
// Perform DML with error handling if
(!caseList.isEmpty()) {
try {
update caseList;
}
catch (DmlException e) {
System.debug('Error while updating cases: ' + e.getMessage());
}
}
}
}

Trigger
trigger AccountTrigger on Account (after update) {
AccountHandler obj = new AccountHandler();
obj.doAction();
}

After Delete
Scenario: When a Department record is deleted, its related Employee records
should also be deleted.

Apex Trigger Handler


public class DepartmentTriggerHandler {

List<Department c> triggerNew;


List<Department c> triggerOld; Map<Id,
Department c> triggerNewMap;
Map<Id, Department c> triggerOldMap;

// Static list to store related employee records for cascading


delete
static List<Employee_Management c> employeeList = new
List<Employee_Management c>();

// Constructor to initialize trigger context variables public


DepartmentTriggerHandler() {
triggerNew = (List<Department c>) Trigger.New; triggerOld =
(List<Department c>) Trigger.Old; triggerNewMap =
(Map<Id, Department c>) Trigger.NewMap;
triggerOldMap = (Map<Id, Department c>) Trigger.OldMap;
}

// Main action handler public void


doAction() {
switch on Trigger.operationType { when
BEFORE_DELETE {
onBeforeDelete();
}

when AFTER_DELETE {
onAfterDelete();
}
}
}

// Method to collect employee records related to departments marked for


deletion
public void onBeforeDelete() {
Set<Id> departmentIds = new Set<Id>();

// Collect all IDs of departments being deleted for (Department


c dept : triggerOld) {
departmentIds.add(dept.Id);
}

// Query related employees whose department is in the list of department IDs


being deleted
employeeList = [SELECT Id, Name FROM Employee_Management c
WHERE Departments c IN :departmentIds];
}

// Method to delete collected employee records public void


onAfterDelete() {
if (!employeeList.isEmpty()) { try {
delete employeeList;
}
catch (DmlException e) {
System.debug('Error deleting employees: ' + e.getMessage());
}
}
}
}

Trigger
trigger DepartmentTrigger on Department c (before delete, after delete) {
DepartmentTriggerHandler handler = new DepartmentTriggerHandler();
handler.doAction();
}

Note: Using static for employee List allows the variable to be shared consistently within the
same transaction, preserving data integrity across trigger executions, preventing redundant
processing, and safeguarding against unintended modifications. This approach is particularly
valuable in Salesforce’s bulk processing environment, where efficiency and adherence to
governor limits are critical.

Undelete

Scenario: When a Department record is undeleted, restore the related


Employee records as well.

Apex Trigger Handler


public class DepartmentTriggerHandler {

List<Department c> triggerNew;


List<Department c> triggerOld; Map<Id,
Department c> triggerNewMap;
Map<Id, Department c> triggerOldMap;

// Static list to store related employee records for cascading delete


static List<Employee_Management c> employeeList = new
List<Employee_Management c>();
// Constructor to initialize trigger context variables public
DepartmentTriggerHandler() {
triggerNew = (List<Department c>) Trigger.New; triggerOld =
(List<Department c>) Trigger.Old; triggerNewMap =
(Map<Id, Department c>) Trigger.NewMap;
triggerOldMap = (Map<Id, Department c>) Trigger.OldMap;

// Main action handler public


void doAction() {
switch on Trigger.operationType { when
AFTER_UNDELETE {
onAfterUnDelete();
}
}
}

public void onAfterUndelete() {


List<Department c> undeletedDepartments = Trigger.new;

// Fetch backup records related to the undeleted Department records


List<Deleted_Employee_Backup c> backupRecords = [ SELECT
DepartmentId c, EmployeeName c,
EmployeeTitle c
FROM Deleted_Employee_Backup c
WHERE DepartmentId c IN :undeletedDepartments
];

// List to hold recreated Employee records List<Employee_Management c>


employeesToRestore = new
List<Employee_Management c>();

// Loop through backup records and recreate Employee records for


(Deleted_Employee_Backup c backup : backupRecords) {
Employee_Management c employee = new Employee_Management c();
employee.Department c = backup.DepartmentId c;
employee.Name = backup.EmployeeName c; employee.Title
c = backup.EmployeeTitle c;

// Add other fields as needed from backup

employeesToRestore.add(employee);
}
// Insert recreated Employee records and handle any errors try {
if (!employeesToRestore.isEmpty()) {
insert employeesToRestore;
}
} catch (DmlException e) {
System.debug('Exception while restoring employees: ' + e.getMessage());
}
}
}

Trigger
trigger DepartmentTrigger on Department c (after undelete) {
DepartmentTriggerHandler handler = new
DepartmentTriggerHandler();
handler.onAfterUndelete();
}

Notes

● Backup Object: This requires a custom object, Deleted_Contact_Backup c, where each


deleted Contact record is stored when its Account is deleted. Make sure the backup
records are created at the time of deletion to allow restoration.
● Cleanup: After restoring, you may want to delete the backup records related to the
restored Contacts.

Another way
Apex Trigger Handler
public class DepartmentTriggerHandler {

List<Department c> triggerNew;


List<Department c> triggerOld; Map<Id,
Department c> triggerNewMap;
Map<Id, Department c> triggerOldMap;

// Static list to store related employee records for cascading delete


static List<Employee_Management c> employeeList = new
List<Employee_Management c>();

// Constructor to initialize trigger context variables public


DepartmentTriggerHandler() {
triggerNew = (List<Department c>) Trigger.New; triggerOld =
(List<Department c>) Trigger.Old; triggerNewMap =
(Map<Id, Department c>) Trigger.NewMap;
triggerOldMap = (Map<Id, Department c>) Trigger.OldMap;
// Main action handler public void
doAction() {
switch on Trigger.operationType {

when AFTER_UNDELETE {
onAfterUnDelete();
}
}
}

public void onAfterUnDelete() {


// Query Employee records related to undeleted Departments in the Recycle Bin
List<Employee_Management c> empList = [
SELECT Id, Department c
FROM Employee_Management c
WHERE Department c IN :Trigger.new AND IsDeleted = true ALL
ROWS
];

try {
// If there are deleted Employee records, undelete them if
(!empList.isEmpty()) {
undelete empList;
}
} catch (DmlException e) {
System.debug('Exception while undeleting employees: '
+ e.getMessage());
}
}
}

Trigger
trigger DepartmentTrigger on Department c (after undelete) {
DepartmentTriggerHandler handler = new
DepartmentTriggerHandler();
handler.onAfterUnDelete();
}
Rollup Summary
Scenario: When Opportunity stage changed into negotiation/review then update the
opportunities count in the Account Object

Apex Trigger Handler


public class OpportunityTriggerHandler { List<Opportunity>
triggerNew; List<Opportunity> triggerOld;
Map<Id, Opportunity> triggerNewMap; Map<Id,
Opportunity> triggerOldMap;

// Constructor to initialize trigger context variables public


DepartmentTriggerHandler() {
triggerNew = (List<Opportunity>) Trigger.New; triggerOld =
(List<Opportunity>) Trigger.Old; triggerNewMap = (Map<Id,
Opportunity>) Trigger.NewMap; triggerOldMap = (Map<Id,
Opportunity>) Trigger.OldMap;
}

// Main action handler public


void doAction() {
Switch on Trigger.OperationType
{
When AFTER_INSERT
{
updateCountOfOpportunity();

}
}
}
public static void updateCountOfOpportunity()
{
//1. Collect Unique Parent Ids Set<Id>
accountIds=new Set<Id>(); for(Opportunity
oppRecord:triggernew)
{
if(oppRecord.AccountId!=null)
{
//1. update - Compare Old and new values
//2. Insert, Delete, Undelete - No need to compare if(Trigger.isupdate)
{

if(Trigger.OldMap.get(oppRecord.Id).StageName!=oppRecord.StageName)
accountIds.add(oppRecord.AccountId);
}
else
{
accountIds.add(oppRecord.AccountId);
}
}
}

//2.Count - Aggregate Function


List<AggregateResult> opportunityResult=[Select Count(Id)
numberOfOpportunities, AccountId
FROM Opportunity
Where StageName =:Label.OpportunityStageNameChange and
AccountId in:accountIds
Group By AccountId];
];
//3.Update Parent Account With Opportunity Count
//Account Id, Count Opportunity
//It Should be SObject record
//Construct Account record from Aggregate Result List<Account>
accountList=new List<Account>(); for(AggregateResult
opp:opportunityResult)
{
Account accountRecord=new Account();//Empty record in
memory
accountRecord.Id=(Id)opp.get('AccountId');//Object--Id

accountRecord.OpportuniyCount c=(Decimal)opp.get('numberOfOpportunitie s');


accountList.add(accountRecord);
}

// 4. Update the Account records


if (!accountList.isEmpty()) { try {
update accountList;
} catch (DmlException e) {
System.debug('Error updating Account records: ' + e.getMessage());
}
}
}
}
Trigger
trigger OpportunityTrigger on Opportunity (after insert, after update, after delete,
after undelete)
{
OpportunityTriggerHandler handler=new OpportunityTriggerHandler();
handler.doAction();
}

Recursive Handler
Scenario Overview:
You have the following set up:

1. Flow: Automatically sets Rating = Hot on an Account when Customer Priority = High.
2. Before Update Trigger: Checks if the Account Type is updated to Customer - Direct. If
true, it sets Customer Priority = High.
3. After Update Trigger: Creates a follow-up task for the Account if certain criteria are
met.

Execution Flow and Recursion:

Given Salesforce’s order of execution, here’s how this scenario unfolds:

1. Initial Update to Account Record: An update to the Account record occurs (e.g.,
changing Type to Customer - Direct).
2. Before Update Trigger (First Run):
○ The trigger detects that Type is set to Customer - Direct, so it sets Customer
Priority = High.
3. After Update Trigger (First Run):
○ Since Customer Priority is now High, the After Update Trigger creates a follow-up
task for this Account.
4. Flow (After-Save):
○ The Flow detects Customer Priority = High and sets Rating = Hot on the Account.
5. Before and After Triggers Execute Again:
○ The Flow’s update to Rating = Hot causes Salesforce to re-run the Before Update
and After Update triggers for this record.
○ The Before Update Trigger might again try to set Customer Priority = High, and
the After Update Trigger might attempt to create another follow-up task.
6. Infinite Loop:
○ The combination of the Flow update and the triggers can create a loop where
Salesforce repeatedly updates the Account and creates tasks in an endless cycle.

Solution: Avoiding Recursion with a Recursive Control Mechanism


To prevent this looping behavior, we use a recursive check. This mechanism ensures that the
triggers only execute certain actions (like creating tasks) once per transaction.

Recursive Handler

Public class AccountRecursiveCheck {


Public static Set<Id> setIds = new Set<Id>();
}

Trigger

trigger AccountTrigger on Account (before update, after update)


{
// Before Update Trigger: Set Customer Priority for specific account types
if (trigger.isBefore && trigger.isUpdate) { for (Account acc :
Trigger.new) {
if (acc.Type == 'Customer - Direct') {
acc.CustomerPriority c = 'High';
}
}
}

// After Update Trigger: Create follow-up Task if criteria are met and avoid recursion
if (trigger.isAfter && trigger.isUpdate) { List<Task> taskList = new
List<Task>();

for (Account acc : Trigger.new) {


// Check if the Account ID has already been processed if
(!AccountRecursiveCheck.setIds.contains(acc.Id)) {
AccountRecursiveCheck.setIds.add(acc.Id);
// Create a follow-up Task for each Account not previously processed Task taskObj = new
Task();
taskObj.WhatId = acc.Id; taskObj.Subject
= 'Followup'; taskList.add(taskObj);
}
}

// Insert Tasks if there are any to insert if


(!taskList.isEmpty()) {
insert taskList;
}

// Clear the set of processed Account IDs after completion


AccountRecursiveCheck.setIds.clear();
}
}

Interview Questions

1. What is a Salesforce trigger?

A Salesforce trigger is a component of Apex code that runs before or after specified data
manipulation language (DML) events, such as before object records are inserted into the
database, even after records are deleted, and so on. Triggers are used to execute custom actions
before or following changes to Salesforce records.

2. What is a Trigger Code?

A Trigger code in Salesforce is an element of Apex code that runs before or after particular data
manipulation language (DML) events. These events include Salesforce records insert, update,
deletes, and undelete. The trigger code is used to implement custom logic, such as data
validation, automation, or modification, in response to various DML events.

3. What are the types of Salesforce Triggers?

There are two types of Triggers

Before Triggers: These are called before the DML process on the database is completed. They
are commonly used to validate or change data before it is saved.

After Triggers: These triggers are executed after the DML operation and data temporarily
saved into the database. They can be used when accessing system-set field values (such as a
recordId or LastModifiedDate field) or modifying other documents based on the initial record’s
actions.

4. Can you explain the trigger execution order in


Salesforce?

Salesforce executes the following in order:

● Load / Initialize Record


● System Validations
● Before Trigger
● Custom Validations
● Duplicate Rules
● Save / No Commit
● After Trigger
● Assignment Rules
● Auto-Response Rules
● Workflow Actions
● Update Record
● System Validations (re-validation occurs here)
● Processes and Flows
● Escalation Rules
● Entitlement Rules
● Parent Roll-up Summaries
● Grandparent Roll-up Summaries
● Criteria-Based Sharing
● Database Commit
● After Commit Logic

5. What is the Trigger.new and Trigger.old context


variable?

Trigger New: It holds the list of new records to be inserted or updated.

Trigger Old: It has the list of old records values before they were updated or deleted.

6. Can triggers be bulkified in Salesforce?

Yes, triggers should always be written with bulk processing in mind, meaning they should
handle multiple records at once. Using loops or SOQL Queries inside loops can cause issues
with governor Limits so developers need to optimize triggers for bulk operations.

7. What are recursive triggers, and how can you avoid


them?

A recursive trigger shows up when a trigger calls itself, that leads to an infinite loop. You can
avoid recursion by using a static boolean variable to track whether the Trigger has already run.
8. What is the use of Trigger.isExecuting?

Trigger.isExecuting is a boolean that returns true if the current context is a trigger, that will
eventually help to check whether your code is running in a trigger context.

9. State the difference between Trigger.new and


Trigger.newMap.

Trigger.new is a list of records with new values. On the other hand, Trigger.New.Map is a map
of IDs to records. This is useful when accessing records using their IDs for processing.

10. Can you use DML operations in triggers?

Yes, you can use DML operations in triggers. However, the best practice is to limit the use of
DML operations to avoid hitting Salesforce governor limits. Using collections to handle bulk
records in DML is recommended.

11. How would you stop a Trigger from executing


multiple times?

You can utilize static variables to prevent a trigger from being executed more than once. A static
variable serves as a flag. You can set this flag after the Trigger has been executed. You can skip
the logic on subsequent recursive calls to the Trigger by checking the flag’s value.

This process ensures that the Trigger is not executed repeatedly within the same transaction,
preventing undesirable behavior or failures.

13. How do you ensure that your Triggers are bulk-safe?

Avoid performing DML actions (insert, update, and delete) or SOQL searches within loops to
ensure your triggers are bulk-safe. This can soon exceed Salesforce governor restrictions,
mainly when dealing with many records. Alternatively, you should:

● Collect records or data into collections (like lists or maps).


● Perform DML or query operations on the entire collection outside of the loop.

So, Trigger can handle mass activities efficiently without experiencing performance difficulties.
14. What are context variables in Salesforce Triggers?

context variables in Salesforce Triggers give critical information about the state of the records
being processed and the runtime context in which the Trigger is executed. Some standard
context variables are:

Trigger. New: Contains the most recent versions of the records inserted or changed.

Trigger. Old: Contains previous versions of the records being updated or destroyed.

Trigger.isInsert, Trigger.isUpdate, and Trigger.Delete: Indicate the type of DML activity that
prompted the Trigger to run.

Trigger.isBefore, Trigger.isAfter: Determine whether the Trigger executes before or after the
DML action.

These variables enable developers to handle multiple scenarios efficiently within a single
Trigger.

15. Can you control multiple Triggers for the same


object?

Salesforce platform permits you to have numerous Triggers on the same object. However, the
hierarchy in which the various triggers are executed is not guaranteed. This can lead to
unpredictability in how the triggers interact.

Salesforce recommends that each item have only one Trigger to avoid potential complications.
A Trigger Handler class allows you to regulate the sequence and execution of your logic easily.

16. How do you test Triggers in Salesforce?

To test Triggers in Salesforce, you write test classes and methods. In the test methods, you
create test data and perform DML operations that invoke the Trigger. You also use
System.assert methods to verify the Trigger’s behavior.

17. How can you call a batch class from a Trigger?

While it’s not recommended due to potential governor limit issues, you can call a batch class
from a Trigger using the Database.executeBatch method. A better practice is to use a queueable
or future method to handle asynchronous processing.
18. When would you use a Trigger instead of Workflow,
Process Builder, or Flows?

You would use a Trigger instead of Workflow, Process Builder, or Flows when dealing with
complex business logic that cannot be handled by these declarative tools or when you need to
create or manipulate records related to the one being processed.

19. How can you handle exceptions in Salesforce


Triggers?

Exceptions in Salesforce Triggers can be handled using try-catch blocks. In the try block, you
write the code which might throw an exception, and in the catch block, you handle the
exception. This can involve adding an error message to the record or logging the error for
review.

20. What is the purpose of a Trigger handler class?

The purpose of a Trigger handler class is to separate the logic of your Trigger from the Trigger
itself. This separation leads to code that is easier to maintain and test. The handler class contains
the methods that carry out the operations needed when the Trigger fires.

21. What happens if a Trigger causes a runtime


exception?

If a Trigger causes a runtime exception, the entire transaction is rolled back. This includes all
DML operations and changes in governor limit usage.

22. How do you debug a Trigger in Salesforce?

To debug a Trigger in Salesforce, you can use debug logs. You can add System.debug
statements in your Trigger code, and then check the logs after executing the operations that fire
the Trigger.
23. What are the limitations of using Triggers in
Salesforce?

Triggers in Salesforce are subject to several limitations:

● Triggers are not suitable for time-consuming operations as they can hit governor limits.
● They can lead to recursion if not handled properly.
● Triggers run on all records of a batch operation, so selective processing can be
challenging.
● Debugging Triggers can be difficult, particularly in production.
● Overutilization of Triggers can lead to complex order of execution scenarios.

24. Can you use SOQL queries in a Trigger? What are


the best practices?

Yes, SOQL queries can be used in a Trigger, but it’s important to follow best practices to avoid
hitting governor limits. These include bulkifying the Trigger to handle multiple records
efficiently, avoiding SOQL queries inside loops, and ensuring that the total number of SOQL
queries does not exceed the limit in a single transaction. Using collections to store data and
querying outside of loops is recommended

You might also like