function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Ayush RoyAyush Roy 

How to bulkify a trigger.

Hi all,
I have written a trigger on Tenant_Details__c object which has a lookup relationship to Room_Details__c object. Every time a new record is inserted, I want to update a field in Room_Details__c and all the Tenants present in that Room in which that tenant has been added. My trigger is working when I insert an individual record, but when I try to enter multiple records simultaneously, then it throws an error. The reason is that it is not bulkified. Con someone please help me bulkify this code.

trigger addTenant on Tenant_Details__c (after insert) 
{   
    List<Tenant_Details__c> tenantDetailList = [select Contribution__c, Stays_in__c from Tenant_Details__c where id in :Trigger.new];
    
    List<Tenant_Details__c> tenantDetailListToUpdate = new List<Tenant_Details__c>();
    List<Room_Details__c> roomDetailListToUpdate = new List<Room_Details__c>();
    
    for(Tenant_Details__c tenant : tenantDetailList)
    {
        Room_Details__c rooms=[select Rent__c, Sharing_Capacity__c, Present_Tenant_Count__c from Room_Details__c where id = :tenant.Stays_in__c];
        
        System.debug('Trigger.isInsert');
        System.debug('Previous tenant count:'+rooms.Present_Tenant_Count__c);
        if(rooms.Present_Tenant_Count__c<rooms.Sharing_Capacity__c)
        {
            rooms.Present_Tenant_Count__c=rooms.Present_Tenant_Count__c+1;//3. updating tenant count 
            
        }
        else
        {
            System.debug('Maximum tenant count reached');
            Trigger.oldMap.get(rooms.Id).addError('Cannot add tenants to this room.');//4. add error on max capacity
        }
        
        //5. increase rent of each tenant
        System.debug('New tenant count: '+rooms.Present_Tenant_Count__c);
        Double contri = rooms.Rent__c/rooms.Present_Tenant_Count__c;
        tenant.Contribution__c=contri;     
        
        for(Tenant_Details__c allTenants:[select Contribution__c from Tenant_Details__c where Stays_in__c=:tenant.Stays_in__c])
        {
            allTenants.Contribution__c=contri;
            tenantDetailListToUpdate.add(allTenants);
        }
		
        roomDetailListToUpdate.add(rooms);
    }
    update roomDetailListToUpdate;
    update tenantDetailListToUpdate;
	
}
Best Answer chosen by Ayush Roy
Christan G 4Christan G 4
Hi Ayush, after reviewing your code, I see it violates some of Salesforce best practices. Here are my suggestions:

1 - Take the code you written and put it into a separate Apex class. As a best practice, one should never write code within an Apex Trigger. In the Apex Trigger, one should only specify what method to call from the handler class.

2 - Always define SOQL statements outside of for loops. The reason is Salesforce imposes a governor limit of 100 SOQL queries per transcation. I believe this is why you are receiving errors when processing large amounts of records.

I've made the necessary changes to your code and put comments.. If you have any questions, please feel free to reach out to me.

Please view below:

Apex Class:
public class TenantDetailTriggerHandler() {

public static void addTenant(Map <ID, Tenant_Details__c> newTenMap, Map <ID, Tenant_Details__c> oldTenMap) {


//Within one SOQL, I am not only capturing the tenant records but also each of their associated child objects (room detail records). I assume there is a one to many relationship from tenant to room detail. If I am wrong please inform me.

//This SOQL may give an error since I haven't tested it out. Writing these types of SOQL
//can be tricky but worth it in the end. 
List<Tenant_Details__c> tenantDetailList = [select Contribution__c, Stays_in__c, (SELECT ID, Rent__c, Sharing_Capacity__c, Present_Tenant_Count__c from Room_Detailss__r) from Tenant_Details__c where id in :newTenMap.keyset()];
    
    List<Tenant_Details__c> tenantDetailListToUpdate = new List<Tenant_Details__c>();
    List<Room_Details__c> roomDetailListToUpdate = new List<Room_Details__c>();
    

    for(Tenant_Details__c tenant : tenantDetailList)
    {
      
        //This SOQL is what was causing an error and limiting how many records code be processed
        //I moved this code above the for loop and commented it out
        //Room_Details__c rooms=[select Rent__c, Sharing_Capacity__c, //Present_Tenant_Count__c from Room_Details__c where id = :tenant.Stays_in__c];
        
       for (Room_Details__c oneRoom : tenant.Room_Details__c) {


        //Changed all references of "rooms" to "oneRoom"
        System.debug('Trigger.isInsert');
        System.debug('Previous tenant count:'+oneRoom.Present_Tenant_Count__c);
        if(oneRoom.Present_Tenant_Count__c<oneRoom.Sharing_Capacity__c)
        {
            oneRoom.Present_Tenant_Count__c=oneRoom.Present_Tenant_Count__c+1;//3. updating tenant count 
            
        }
        else
        {
            System.debug('Maximum tenant count reached');

            //Replaced "Trigger.oldMap" with "oldTenMap"
            //After reviewing this again, this may also give an error. The reason is when we pass the room
//id as an argument in the get method, it'll return NullException since the Trigger.oldMap is a map of the //Tenant_Details__c object and not the Room_Details__c object.
//To resolve this, you should replace "oldTenMap.get(oneRoom.Id)" with just "oneRoom" 
            oldTenMap.get(oneRoom.Id).addError('Cannot add tenants to this room.');//4. add error on max capacity
        }
        
        //5. increase rent of each tenant
        System.debug('New tenant count: '+rooms.Present_Tenant_Count__c);
        Double contri = rooms.Rent__c/rooms.Present_Tenant_Count__c;
        tenant.Contribution__c=contri;     
        
        for(Tenant_Details__c allTenants:[select Contribution__c from Tenant_Details__c where Stays_in__c=:tenant.Stays_in__c])
        {
            allTenants.Contribution__c=contri;
            tenantDetailListToUpdate.add(allTenants);
        }
		
        roomDetailListToUpdate.add(rooms);
    }
    update roomDetailListToUpdate;
    update tenantDetailListToUpdate;	
}
}
}

Apex Trigger:
trigger TenantDetailTrigger on Tenant_Details__c (after insert) 
{   
    TenantDetailTriggerHandler.addTenant(Trigger.newMap, Trigger.oldMap);
}

All Answers

Christan G 4Christan G 4
Hi Ayush, after reviewing your code, I see it violates some of Salesforce best practices. Here are my suggestions:

1 - Take the code you written and put it into a separate Apex class. As a best practice, one should never write code within an Apex Trigger. In the Apex Trigger, one should only specify what method to call from the handler class.

2 - Always define SOQL statements outside of for loops. The reason is Salesforce imposes a governor limit of 100 SOQL queries per transcation. I believe this is why you are receiving errors when processing large amounts of records.

I've made the necessary changes to your code and put comments.. If you have any questions, please feel free to reach out to me.

Please view below:

Apex Class:
public class TenantDetailTriggerHandler() {

public static void addTenant(Map <ID, Tenant_Details__c> newTenMap, Map <ID, Tenant_Details__c> oldTenMap) {


//Within one SOQL, I am not only capturing the tenant records but also each of their associated child objects (room detail records). I assume there is a one to many relationship from tenant to room detail. If I am wrong please inform me.

//This SOQL may give an error since I haven't tested it out. Writing these types of SOQL
//can be tricky but worth it in the end. 
List<Tenant_Details__c> tenantDetailList = [select Contribution__c, Stays_in__c, (SELECT ID, Rent__c, Sharing_Capacity__c, Present_Tenant_Count__c from Room_Detailss__r) from Tenant_Details__c where id in :newTenMap.keyset()];
    
    List<Tenant_Details__c> tenantDetailListToUpdate = new List<Tenant_Details__c>();
    List<Room_Details__c> roomDetailListToUpdate = new List<Room_Details__c>();
    

    for(Tenant_Details__c tenant : tenantDetailList)
    {
      
        //This SOQL is what was causing an error and limiting how many records code be processed
        //I moved this code above the for loop and commented it out
        //Room_Details__c rooms=[select Rent__c, Sharing_Capacity__c, //Present_Tenant_Count__c from Room_Details__c where id = :tenant.Stays_in__c];
        
       for (Room_Details__c oneRoom : tenant.Room_Details__c) {


        //Changed all references of "rooms" to "oneRoom"
        System.debug('Trigger.isInsert');
        System.debug('Previous tenant count:'+oneRoom.Present_Tenant_Count__c);
        if(oneRoom.Present_Tenant_Count__c<oneRoom.Sharing_Capacity__c)
        {
            oneRoom.Present_Tenant_Count__c=oneRoom.Present_Tenant_Count__c+1;//3. updating tenant count 
            
        }
        else
        {
            System.debug('Maximum tenant count reached');

            //Replaced "Trigger.oldMap" with "oldTenMap"
            //After reviewing this again, this may also give an error. The reason is when we pass the room
//id as an argument in the get method, it'll return NullException since the Trigger.oldMap is a map of the //Tenant_Details__c object and not the Room_Details__c object.
//To resolve this, you should replace "oldTenMap.get(oneRoom.Id)" with just "oneRoom" 
            oldTenMap.get(oneRoom.Id).addError('Cannot add tenants to this room.');//4. add error on max capacity
        }
        
        //5. increase rent of each tenant
        System.debug('New tenant count: '+rooms.Present_Tenant_Count__c);
        Double contri = rooms.Rent__c/rooms.Present_Tenant_Count__c;
        tenant.Contribution__c=contri;     
        
        for(Tenant_Details__c allTenants:[select Contribution__c from Tenant_Details__c where Stays_in__c=:tenant.Stays_in__c])
        {
            allTenants.Contribution__c=contri;
            tenantDetailListToUpdate.add(allTenants);
        }
		
        roomDetailListToUpdate.add(rooms);
    }
    update roomDetailListToUpdate;
    update tenantDetailListToUpdate;	
}
}
}

Apex Trigger:
trigger TenantDetailTrigger on Tenant_Details__c (after insert) 
{   
    TenantDetailTriggerHandler.addTenant(Trigger.newMap, Trigger.oldMap);
}
This was selected as the best answer
Christan G 4Christan G 4
I just realized that I forgot to remove the paranthesises after "TenantDetailTriggerHandler" at the beginning of the Apex class. Please remove those paranthesises when copying and pasting. Sorry for the inconvenience
Ayush RoyAyush Roy
Hi Christan,
Thanks for the quick reply. There was a small issue in Line 10. Actually the Parent object is the Room_Details__c and it can have multiple child objects of Room_Details__c. And Tenant_Details__c has a Lookup Relationship to Rooms_Details__c. Since I am new to Salesforce, I am not able to rectify it on my own. 
Thanks for your help.
sunnny nicsunnny nic
01trigger accountTestTrggr on Account (before insert, before update) {
02 
03   //This only handles the first record in the Trigger.new collection
04   //But if more than 1 Account initiated this trigger, those additional records
05   //will not be processed
06   Account acct = Trigger.new[0];
07   List<Contact> contacts = [select id, salutation, firstname, lastname, email
08              from Contact where accountId = :acct.Id];
09    
10}
The issue is that only one Account record is handled because the code explicitly accesses only the first record in the Trigger.new collection by using the syntax Trigger.new[0]. Instead, the trigger should properly handle the entire collection of Accounts in the Trigger.new collection.
Here is a sample of how to handle all incoming records:
view sourceprint?
01trigger accountTestTrggr on Account (before insert, before update) {
02 
03   List<String> accountNames = new List<String>{};
04  
05   //Loop through all records in the Trigger.new collection
06   for(Account a: Trigger.new){
07      //Concatenate the Name and billingState into the Description field
08      a.Description = a.Name + ':' + a.BillingState
09   }
10    
11}

https://www.walgreenlistens.one/
Christan G 4Christan G 4
Hi Ayush, sorry for the late reply. If you like, we can troubleshoot the issue regarding line 10. Also, regarding what you mentioned about the Room_Details__c, does it have a look up to itself? I ask because you stated that it was the parent but also stated that it can have multiple child objects of itself. Just to be sure, the Tenant_Details__c is a child object of the Room_Details__c object correct?