Intermediate 30 min read

Build a Bulk-Safe Trigger

By the end of this lesson, you'll be able to:

  • Write the thin trigger and handler skeleton for Leave_Request__c
  • Query every existing leave request for the affected employees, once
  • Explain why this query happens before, not during, the per-record validation loop

Prerequisites: "Design the Approval Workflow"

The thin trigger

trigger LeaveRequestTrigger on Leave_Request__c (before insert) {
    new LeaveRequestTriggerHandler().beforeInsert(Trigger.new);
}
  1. trigger LeaveRequestTrigger on Leave_Request__c (before insert) { — exactly Module 24's thin-trigger pattern — before insert, since overlap validation must block the save (Module 26's addError() lesson), which only works in a before context.
  2. new LeaveRequestTriggerHandler().beforeInsert(Trigger.new); — hands off to the handler.
  3. } — closes the trigger.

Fetching existing requests in one query

public void beforeInsert(List<Leave_Request__c> newRequests) {
    Set<String> employeeNames = new Set<String>();
    for (Leave_Request__c req : newRequests) {
        employeeNames.add(req.Employee_Name__c);
    }

    List<Leave_Request__c> existingRequests = [
        SELECT Id, Employee_Name__c, Start_Date__c, End_Date__c
        FROM Leave_Request__c
        WHERE Employee_Name__c IN :employeeNames AND Status__c != 'Rejected'
    ];
    // ... validation logic uses existingRequests (next lessons) ...
}
  1. public void beforeInsert(List<Leave_Request__c> newRequests) { — the method.
  2. Set<String> employeeNames = new Set<String>(); — this is Module 16's Set deduplication.
  3. for (Leave_Request__c req : newRequests) { — loops over the new requests, in memory.
  4. employeeNames.add(req.Employee_Name__c); — collects every affected employee's name once.
  5. } — closes the loop.
  6. (blank line)
  7. List<Leave_Request__c> existingRequests = [ — one upfront query.
  8. SELECT Id, Employee_Name__c, Start_Date__c, End_Date__c — the fields needed.
  9. FROM Leave_Request__c — the object.
  10. WHERE Employee_Name__c IN :employeeNames AND Status__c != 'Rejected' — all of their existing, non-rejected requests in a single statement.
  11. ]; — closes the query.
  12. // ... validation logic uses existingRequests (next lessons) ... — the next step, before any per-record comparison starts.
  13. } — closes the method.

Why the query happens before the validation loop

// WRONG — a query per new request, exactly the anti-pattern from Module 25
for (Leave_Request__c req : newRequests) {
    List<Leave_Request__c> existing = [SELECT Id FROM Leave_Request__c WHERE Employee_Name__c = :req.Employee_Name__c];
    // ...
}
  1. // WRONG — a query per new request, exactly the anti-pattern from Module 25 — the label says it all.
  2. for (Leave_Request__c req : newRequests) { — loops over the new requests.
  3. List<Leave_Request__c> existing = [SELECT Id FROM Leave_Request__c WHERE Employee_Name__c = :req.Employee_Name__c]; — querying inside the per-record loop would be the SOQL-in-a-loop problem from Module 25, at exactly the scale this feature is most likely to face — a bulk data load inserting hundreds of leave requests at once.
  4. // ... — the rest of the loop body.
  5. } — closes the loop.

Fetching everything needed before the loop begins is what keeps the actual comparison loop (next lesson) free of any database operation at all.

Exercise

Given a List<Leave_Request__c> newRequests, write the Set<String> collection of unique Employee_Name__c values.

Show hint

Loop over newRequests, adding each Employee_Name__c to a Set<String>.

Salesforce: ○ Not connected Connect
APEX

Execution Results

Run your code to see the result.

Build a Bulk-Safe Trigger Quiz

1. Why query existing requests before the per-record validation loop, rather than inside it?

Log in to submit the quiz and save your score.

My Notes

Log in to keep private notes on this lesson.

Questions about this lesson

No questions yet — be the first to ask.

Log in to ask a question about this lesson.

Summary

This lesson sets up the trigger, handler, and — critically — the single upfront query that fetches every existing leave request the validation logic will need, before any per-record checking begins.