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
Wendy SadehWendy Sadeh 

trigger update objects

I have a custom field that will store the order of a related list off of the Opportunity Object.  So if item gets udpated the trigger should run through and adjust the order.  I am running into recursion errors.  I have googled for a while and haven't found a pattern that will work for me.  Any suggestion?

trigger updateTenantSortOrder on Tenant__c (before insert, before update, after delete) {
    Set<Id> opportunityIds = new Set<Id>();
    Integer order=1;

    for (Tenant__c tenant : Trigger.new) {
        // get opportunity id to be used as a key to get entire list.
        opportunityIds.add(tenant.Opportunity__c);
    }

   // Order list appropriately
    List<Tenant__c> tenants = 
          [select id,Name,TenantBySqFtSortOder__c from Tenant__c where Opportunity__c IN :opportunityIds order by Tenant_Square_Foot__c DESC LIMIT 10];

   // Update sort order field
    for (Tenant__c t : tenants) {
            t.TenantBySqFtSortOder__c =order++;
    }

    // update objects
    update tenants;
}

 

Best Answer chosen by Wendy Sadeh
SFDC EvangelistSFDC Evangelist
Hi Wendy,

Here you are trying to using a BEFORE trigger, and in your trigger, you are trying to do a DML call on records that are in the trigger. In BEFORE triggers, any manipulation you do to the data happens before the records undergo their DML call, so you don't need to perform a DML call on the trigger records -- it's already gonna happen. However, you would need to do a DML call on AFTER triggers. They happen after the trigger, so anything you do AFTER the trigger needs to be "saved" via a DML call.

To note, I usually use BEFORE triggers if I need to alter data that is being fed into the trigger while I use AFTER triggers if I need to alter data that is related to the data being fed into the trigger.

Also, use separate Helper class to write the logic and put a static variable to stop the recursiveness there. You can not "update tenants" from Tenant Trigger; which in turn run the Trigger recursively.

To prevent a recursive call, you should make sure your trigger only executes one time. Add a class with a static boolean variable. In the trigger, have a condition that checks the value of the boolean. Once the trigger executes, change the value to false.

Let me know if this helps.

 

All Answers

SFDC EvangelistSFDC Evangelist
Hi Wendy,

Here you are trying to using a BEFORE trigger, and in your trigger, you are trying to do a DML call on records that are in the trigger. In BEFORE triggers, any manipulation you do to the data happens before the records undergo their DML call, so you don't need to perform a DML call on the trigger records -- it's already gonna happen. However, you would need to do a DML call on AFTER triggers. They happen after the trigger, so anything you do AFTER the trigger needs to be "saved" via a DML call.

To note, I usually use BEFORE triggers if I need to alter data that is being fed into the trigger while I use AFTER triggers if I need to alter data that is related to the data being fed into the trigger.

Also, use separate Helper class to write the logic and put a static variable to stop the recursiveness there. You can not "update tenants" from Tenant Trigger; which in turn run the Trigger recursively.

To prevent a recursive call, you should make sure your trigger only executes one time. Add a class with a static boolean variable. In the trigger, have a condition that checks the value of the boolean. Once the trigger executes, change the value to false.

Let me know if this helps.

 
This was selected as the best answer
Wendy SadehWendy Sadeh
Thank you that did help!   Here is the full solution in case it can help others.
trigger updateTenantSortOrder on Tenant__c (after insert, after update, after delete) {
    
    Set<Id> opportunityIds = new Set<Id>();
    
    if(TriggerContextUtility.isFirstRun()){
        // get opportunity id to be used as a key to get entire list.
        if((Trigger.isInsert || Trigger.isUpdate) && Trigger.isAfter){
        	TriggerContextUtility.setFirstRunFalse();
    		for (Tenant__c tenant : Trigger.new) {
        		opportunityIds.add(tenant.Opportunity__c);
    		}
       } else if(Trigger.isDelete && Trigger.isAfter){
           for (Tenant__c tenant : Trigger.old) {
        		opportunityIds.add(tenant.Opportunity__c);
    		}
       }
       updateTenantSortOrderHandler.processUpdate(opportunityIds);
    }
}

public class updateTenantSortOrderHandler {

    public static void processUpdate (Set<ID> opportunityIds){
        Integer order=1;
     
    	List<Tenant__c> tenants = 
      		[select id,Name,TenantBySqFtSortOder__c from Tenant__c where Opportunity__c IN :opportunityIds order by Tenant_Square_Foot__c DESC LIMIT 10];
   
    	for (Tenant__c t : tenants) {
        	t.TenantBySqFtSortOder__c =order++;
    	}
    	update tenants;
    }
}

public class TriggerContextUtility {

    private static boolean firstRun = true;

    public static boolean isFirstRun() {
        return firstRun;
    }
    public static void setFirstRunFalse(){
        firstRun = false;
    }
}
Wendy SadehWendy Sadeh
I thought I would also add the test class.  For the insert case testing revealed a code flaw I fixed and for the delete case I had to manually reset the recurrsion flag so the delete event would trigger.
//This class now gets all tenants for an opp and adds the sort order.
public class updateTenantSortOrderHandler {

    public static void processUpdate (Set<ID> opportunityIds){
        Integer order=1;
        List<Tenant__c> tenantsToUpdate;
        for(Id oppid : opportunityIds){
            List<Tenant__c> tenants = 
      		[select id,Name,TenantBySqFtSortOder__c,Tenant_Square_Foot__c from Tenant__c where Opportunity__c =: oppid order by Tenant_Square_Foot__c DESC LIMIT 10];
    		for (Tenant__c t : tenants) {
            	t.TenantBySqFtSortOder__c =order++;
                }
            update tenants;
            order=1;
        }
    }
}


@isTest
private class updateTenantSortOrderHandlerTest {

    private static testMethod void testInsertRecords() {

        List<Opportunity> opportunities = new List<Opportunity>();
        List<Tenant__c> tenants = new List<Tenant__c>();

        // insert some opportunities
        Opportunity o1 = new Opportunity(name='Opportunity 1',StageName='Pre-Qualified Lead', Project_Type__c='TBD',CloseDate=Date.today()+2,
                Est_Improvement_Cost__c = 50.00, Loan_Term__c = 24);
        Opportunity o2 = new Opportunity(name='Opportunity 2', StageName='Pre-Qualified Lead', Project_Type__c='TBD',CloseDate=Date.today()+2,
                Est_Improvement_Cost__c = 50.00, Loan_Term__c = 24);
        opportunities.add(o1);
        opportunities.add(o2);
        insert opportunities;

        Test.startTest();

        tenants.add(new Tenant__c(Opportunity__c=o1.Id, Name='Shop1', Tenant_Square_Foot__c=100));
        tenants.add(new Tenant__c(Opportunity__c=o1.Id, Name='Shop2', Tenant_Square_Foot__c=200));
        tenants.add(new Tenant__c(Opportunity__c=o2.Id, Name='Shop3', Tenant_Square_Foot__c=300));
        tenants.add(new Tenant__c(Opportunity__c=o2.Id, Name='Shop4', Tenant_Square_Foot__c=400));

        insert tenants;

        Test.stopTest();
        
        List<Tenant__c> tenantsRefreshed = 
      		[select id,Name,TenantBySqFtSortOder__c,Tenant_Square_Foot__c from Tenant__c where id IN :tenants];
 
        for (Tenant__c t : tenantsRefreshed) {
            if(t.Name.equals('Shop1'))
                System.assertEquals(2,t.TenantBySqFtSortOder__c);
            if(t.Name.equals('Shop2'))
                System.assertEquals(1,t.TenantBySqFtSortOder__c);
            if(t.Name.equals('Shop3'))
                System.assertEquals(2,t.TenantBySqFtSortOder__c);
            if(t.Name.equals('Shop4'))
                System.assertEquals(1,t.TenantBySqFtSortOder__c);
        }
       }

    private static testMethod void testDeleteRecords() {

        List<Opportunity> opportunities = new List<Opportunity>();
        List<Tenant__c> tenants = new List<Tenant__c>();

        // insert an opportunities

        Opportunity o1 = new Opportunity(name='Opportunity 1',StageName='Pre-Qualified Lead', Project_Type__c='TBD',CloseDate=Date.today()+2,
                Est_Improvement_Cost__c = 50.00, Loan_Term__c = 24);
        opportunities.add(o1);
        insert opportunities;

        tenants.add(new Tenant__c(Opportunity__c=o1.Id, Name='ShopDelete1', Tenant_Square_Foot__c=100));
        tenants.add(new Tenant__c(Opportunity__c=o1.Id, Name='ShopDelete2', Tenant_Square_Foot__c=200));
        
        insert tenants;
        
        Test.startTest();
        // now delete a record and reset recurrsion flag
        TriggerContextUtility.setFirstRunTrue();
        delete tenants.get(1);
        Test.stopTest();
        
        List<Tenant__c> tenantsRefreshed = 
      		[select id,Name,TenantBySqFtSortOder__c,Tenant_Square_Foot__c from Tenant__c where Opportunity__c=:o1.id];
 
        for (Tenant__c t : tenantsRefreshed) {
            if(t.Name.equals('ShopDelete1'))
                System.assertEquals(1,t.TenantBySqFtSortOder__c);
        }
    }

}