Scenario: We have two custom object Meeting, Guest on Meeting we have field Status (Scheduled, In-Progress, Completed). On Guest we have Meeting lookup. Write logic such as If Meeting Status is ‘Completed’ then we should not able to link Guest to that Meeting.
GuestTrigger
trigger GuestTrigger on cbugs__Guest__c (before insert) {
GuestTriggerHandler handler = new GuestTriggerHandler();
handler.addGuestToMeeting(trigger.new);
} GuestTriggerHandler - Solution 1
public class GuestTriggerHandler {
public void addGuestToMeeting(List<cbugs__Guest__c> lstGuests) {
// Way 1: Not using Query in loop
Set<Id> meeetingIds = new Set<Id>();
for(cbugs__Guest__c guest: lstGuests) {
meeetingIds.add(guest.cbugs__Meeting__c);
}
Map<Id,cbugs__Meeting__c> mapMeetings = new Map<Id,cbugs__Meeting__c>([Select Id, cbugs__Status__c From cbugs__Meeting__c Where Id in: meeetingIds]);
for(cbugs__Guest__c guest: lstGuests) {
cbugs__Meeting__c meeting = mapMeetings.get(guest.cbugs__Meeting__c);
if(meeting.cbugs__Status__c == 'Completed') {
guest.addError('Meeting is already finished.');
}
}
}
} GuestTriggerHandler - Solution 2
public class GuestTriggerHandler {
// Way 2 : Here we are using query in look
public void addGuestToMeeting(List<cbugs__Guest__c> lstGuests) {
Set<Id> meeetingIds = new Set<Id>();
for(cbugs__Guest__c guest: lstGuests) {
List<cbugs__Meeting__c> lstMeetings = [Select Id, cbugs__Status__c From cbugs__Meeting__c Where Id=: guest.cbugs__Meeting__c limit 1];
if(lstMeetings[0].cbugs__Status__c == 'Completed') {
guest.addError('Meeting is already finished.');
}
}
}
} How the two solutions compare
Solution 1 collects all Meeting Ids, runs one query, stores the Meetings in a Map and checks each Guest against the Map. The number of queries stays at one for any number of Guests. Solution 2 queries the Meeting inside the loop, which uses one query for every Guest and fails with more than 100 Guests in a batch. Use Solution 1.
Gaps in Solution 1
- If a Guest has no Meeting,
mapMeetings.get(...)returns null and the next line throws a null pointer error. - The trigger runs only
before insert. A user can still move an existing Guest to a Completed Meeting by editing it.
Improved handler
public class GuestTriggerHandler {
public void addGuestToMeeting(List<cbugs__Guest__c> lstGuests) {
Set<Id> meetingIds = new Set<Id>();
for (cbugs__Guest__c guest : lstGuests) {
if (guest.cbugs__Meeting__c != null) {
meetingIds.add(guest.cbugs__Meeting__c);
}
}
Map<Id, cbugs__Meeting__c> mapMeetings = new Map<Id, cbugs__Meeting__c>(
[SELECT Id, cbugs__Status__c FROM cbugs__Meeting__c WHERE Id IN :meetingIds]
);
for (cbugs__Guest__c guest : lstGuests) {
cbugs__Meeting__c meeting = mapMeetings.get(guest.cbugs__Meeting__c);
if (meeting != null && meeting.cbugs__Status__c == 'Completed') {
guest.addError('Meeting is already finished.');
}
}
}
} trigger GuestTrigger on cbugs__Guest__c (before insert, before update) {
new GuestTriggerHandler().addGuestToMeeting(Trigger.new);
} How to test
- Create a Meeting with status Scheduled and add a Guest. It should save.
- Set the Meeting to Completed and try to add another Guest. You should see the error.
- Edit the first Guest and save. With before update added, the save is blocked if the Meeting is Completed.
FAQ
Could a validation rule do this?
Yes. A validation rule on Guest that checks the Meeting status through a cross-object formula can block the save without code.
Why not query in the loop?
A query in a loop is the most common way to hit the limit of 100 SOQL queries.