+ Start a Discussion
tmbarrytmbarry 

How to Bulkify a Trigger

I recently wrote a trigger that when a Task with a certain subject is closed, create a new follow up task.  Everything was working fine until one of my users bulk loaded a few hundred records and I received an error message.  Now I need to bulkify this trigger and have no idea how.

 

trigger Send_Qtrly_Update_02_v02 on Task (after insert, after update) {
for (Task t : Trigger.new){
   
// Today's Date plus 90 days for scheduling next call
 DateTime dT = System.now()+90;
 Date myDate = date.newinstance(dT.year(), dT.month(), dT.day());
// Today's Date for updating the Last_Outreach_Call_Made__c field on the Provider_Facility__c record.
 DateTime dT1 = System.now();
 Date TodayDate = date.newinstance(dT1.year(), dT1.month(), dT1.day()); 

// Step 1 - Get the Outreach_Agent__c id to assign the task too.  This logic is used incase the Outreach value on the Provider
// Facilty changes in the future.
For (Provider_Facility__c prov: [ Select id, Outreach_Agent__c from Provider_Facility__c Where id =: t.whatid]){
// Now Check to see if the Task meets the right citeria
if (t.Subject == 'Quarterly Outreach Call' && t.IsClosed==True) { 
// Insert a recurring task
        Task t1 = new task(Whatid=t.whatid, Subject='Quarterly Outreach Call', ActivityDate=myDate, Ownerid=prov.Outreach_Agent__c );
        insert t1;
//  Update the Last_Outreach_Call_Made__c field on the Provider_Facility__c record
        Provider_Facility__c prov01 = New Provider_Facility__c(id=t.WhatId, Last_Outreach_Call_Made__c=TodayDate);
        Update prov01;
        }}
}
}

 Can someone point me in the right direction on how to do this?

 

Any help would be much appreciated.  It seems like everytime I learn how to do something, I need to re-learn it to better fit the Apex framework.  

Best Answer chosen by Admin (Salesforce Developers) 
sfdcfoxsfdcfox
trigger Send_Qtrly_Update_02_02 on Task(after insert, after update) {
    // next call date is 90 days out
    Date nextCallDate = DateTime.now().addDays(90).date();
    
    // Tasks that meet criteria, and new tasks to create
    Task[] qualifiedTasks = new Task[0], newTasks = new Task[0];
    
    // Map of provider facilities
    Map<Id,Provider_Facility__c> facilities = new Map<Id,Provider_Facility__c>();

    // Find qualifying tasks
    for(Task record:Trigger.new)
        if(record.subject=='Quarterly Outreach Call' && t.isclosed && record.whatid!=null && record.whatid.getsobjecttype()==provider_facility__c.sobjecttype)
            qualifiedtasks.add(record);
    
    // Obtain facility ID values
    for(Task record:qualifiedtasks)
        facilities.put(record.whatid,null);
    
    // If there are any facilities to query, do so.
    if(!facilities.isempty())
        facilities.putall([select id,outreach_agent__c from provider_facility__c where id in :facilities.keyset()]);
    
    // For each qualifying task, create a new task and update the facility record.
    for(Task record:qualifiedtasks) {
        newTasks.add(new task(whatid=record.whatid,subject='Quarterly Outreach Call',ActivityDate=nextCallDate,OwnerId=facilities.get(record.whatid).Outreach_Agent__c));
        facilities.get(record.whatid).last_outreach_call_made__c=Date.today();
    }
    
    // Insert new task records into database.
    insert newTasks;
    // Update facility last call dates.
    update facilities.values();
}

This encompasses all of the best practices I could think of:

 

* Use a map instead of querying inside a loop.

* Don't use extra variables if you can avoid it.

* Prefilter Trigger.new to find records you will be operating on.

 

This code is basically what I call the "aggregate-query-update" pattern. First, I gather together all the values I need, then I query for them, and then I perform updates based on the queried data. This is the most common form of a trigger, so learning this pattern will make sure your code is always bulkified.

All Answers

sfdcfoxsfdcfox
trigger Send_Qtrly_Update_02_02 on Task(after insert, after update) {
    // next call date is 90 days out
    Date nextCallDate = DateTime.now().addDays(90).date();
    
    // Tasks that meet criteria, and new tasks to create
    Task[] qualifiedTasks = new Task[0], newTasks = new Task[0];
    
    // Map of provider facilities
    Map<Id,Provider_Facility__c> facilities = new Map<Id,Provider_Facility__c>();

    // Find qualifying tasks
    for(Task record:Trigger.new)
        if(record.subject=='Quarterly Outreach Call' && t.isclosed && record.whatid!=null && record.whatid.getsobjecttype()==provider_facility__c.sobjecttype)
            qualifiedtasks.add(record);
    
    // Obtain facility ID values
    for(Task record:qualifiedtasks)
        facilities.put(record.whatid,null);
    
    // If there are any facilities to query, do so.
    if(!facilities.isempty())
        facilities.putall([select id,outreach_agent__c from provider_facility__c where id in :facilities.keyset()]);
    
    // For each qualifying task, create a new task and update the facility record.
    for(Task record:qualifiedtasks) {
        newTasks.add(new task(whatid=record.whatid,subject='Quarterly Outreach Call',ActivityDate=nextCallDate,OwnerId=facilities.get(record.whatid).Outreach_Agent__c));
        facilities.get(record.whatid).last_outreach_call_made__c=Date.today();
    }
    
    // Insert new task records into database.
    insert newTasks;
    // Update facility last call dates.
    update facilities.values();
}

This encompasses all of the best practices I could think of:

 

* Use a map instead of querying inside a loop.

* Don't use extra variables if you can avoid it.

* Prefilter Trigger.new to find records you will be operating on.

 

This code is basically what I call the "aggregate-query-update" pattern. First, I gather together all the values I need, then I query for them, and then I perform updates based on the queried data. This is the most common form of a trigger, so learning this pattern will make sure your code is always bulkified.

This was selected as the best answer
tmbarrytmbarry

Thankd for you help sfdcfox, but I am getting an error message:

 

Error: Invalid Data. 
Review all error messages below to correct your data.
Apex trigger Send_Qtrly_Update_02_02 caused an unexpected exception, contact your administrator: Send_Qtrly_Update_02_02: execution of AfterUpdate caused by: System.DmlException: Insert failed. First exception on row 0; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, Send_Qtrly_Update_02_02: maximum trigger depth exceeded Task trigger event AfterUpdate for [00TW0000006KTi0] Task trigger event AfterInsert for [00TW0000006KTie] Task trigger event AfterInsert for [00TW0000006KTif] Task trigger event AfterInsert for [00TW0000006KTig] Task trigger event AfterInsert for [00TW0000006KTih] Task trigger event AfterInsert for [00TW0000006KTii] Task trigger event AfterInsert for [00TW0000006KTij] Task trigger event AfterInsert for [00TW0000006KTik] Task trigger event AfterInsert for [00TW0000006KTil] Task trigger event AfterInsert for [00TW0000006KTim] Task trigger event AfterInsert for [00TW0000006KTin] Task trigger event AfterInsert for [00TW0000006KTio] Task trigger event AfterInsert for [00TW0000006KTip] Task trigger event AfterInsert for [00TW0000006KTiq] Task trigger event AfterInsert for [00TW0000006KTir] Task trigger event AfterInsert for [00TW0000006KTis]: []: Trigger.Send_Qtrly_Update_02_02: line 31, column 1

 

Any thoughts?

tmbarrytmbarry

And can you explain what this is doing:  

 

&& record.whatid.getsobjecttype()==provider_facility__c.sobjecttype

 

in line 13?

 

Thanks again.

sfdcfoxsfdcfox
1) There wasn't a task status specified, so it might be defaulting to closed status... which would cause an infinite loop. Try setting the Status field to an open status value.

2) The ID class (a primitive object) has a method called getSObjectType(). Basically, it returns an SObjectType (such as Account, Opportunity, etc). This particular fragment returns "false" if WhatId isn't a Provider_Facility__c ID (e.g. if it happens to be an Opportunity).
tmbarrytmbarry

Thansk sfdcfox.  I had to "tweak" it a little, but I got it working!