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
nelrib88nelrib88 

Trigger firing at wrong time

I'm having an issue with my trigger that is confusing me.  My trigger is called on a before update when a specific field on the contact becomes empty.  My trigger code calls a separate @future apex class only if the field i check against begins populated before the save and then becomes empty and then makes a callout to an external system. There is also a third party batch process that does upserts on all contacts every night and updates some fields.  The field that fires off the callout is never modyfied during this batch process.

 

The problem is during the nightly batch process the upserts are causing my trigger to call the @future class causing it to throw an error.

 

caused by: System.DmlException: Update failed. First exception on row 0 with id 0033000000z3r2cAAA; first error: CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY, InvalidEmailContactTrigger: execution of BeforeUpdate

caused by: System.AsyncException: Future method cannot be called from a future or batch method: EmailIssueRemover.removeEmailIssue(MAP<Id,String>, String, String)

 

for(Contact contact : trigger.old)
        { 
            emailissue = contact.Email_Issue__c;
        }
        
        for(Contact contact : trigger.new)
        {            
            if(contact.Email_Issue__c == null && emailissue != null)
            {  
                contactsToUpdate.put(contact.Id, emailissue);
                session = contact.session_id__c;
                server = contact.server_url__c;
                lastName = contact.lastName;
                changed = true;
            } 
        }
        if(lastName == 'ApexRunTestIssue'){
            EmailIssueRemover.test(contactsToUpdate);
        } else {     
            if(changed){
                EmailIssueRemover.removeEmailIssue(contactsToUpdate, session, server);
            }
        }

 

 

Can anyone explain why this might happen.

Best Answer chosen by Admin (Salesforce Developers) 
Starz26Starz26

So heres the deal on your trigger (and yes you should remove the trigger.old loop and replace it with the reccomendation of the previous poster:

 

//If a Before Trigger
if(Trigger.isBefore)
    {
        //You set the initial value to NULL
        emailissue = null;
        //Here you loop through ALL 200 records in the trigger, the value of the last record - Email_Issue__c is saved as the comparison. The problem is this is not performing the trigger on each individual but looping through all 200 and the last records ends up being the value set.
        for(Contact contact : trigger.old)
        { 
            emailissue = contact.Email_Issue__c;
        }
        
        //Now you loop through ALL 200 records in the trigger and for each one you compare the value of Email_Issue__c to the LAST RECORDS VALUE of emailissue (see above). Thus if the last record processed is = null and ANY record in the trigger has an Email_Issue__c == NULL you set the value of changed. In addition, the value of CHANGED is not reset on a per record basis so I would simply check for !contactsToUpdate.isEmpty() and drop the changed flag
        for(Contact contact : trigger.new)
        {            
            if(contact.Email_Issue__c == null && emailissue != null)
            {  
                contactsToUpdate.put(contact.Id, emailissue);
                session = contact.session_id__c;
                server = contact.server_url__c;
                lastName = contact.lastName;
                changed = true;
            } 
        }
        if(lastName == 'ApexRunTestIssue'){
            EmailIssueRemover.test(contactsToUpdate);
        } else {     
            if(changed){
                EmailIssueRemover.removeEmailIssue(contactsToUpdate, session, server);
            }
        }
    }
}

 

All Answers

Damien_Damien_

Batches are considered a Future method because they happen sometime in the future.  It does work on an object causing your trigger to fire.  Your trigger also calls a future method.  Future methods are not allowed to call other future methods.

nelrib88nelrib88

I understand this but the future method should only be called if the field becomes empty.  In the batch process the Email Issue field is never modyfied and thus i would assume that the future method would never be called.

Damien_Damien_

Try putting a debug statement right before ur @future method and see if it prints up.

nelrib88nelrib88

After looking at my code i realize that the issue might have to do with the variable 'changed' being true. 

 

Im not very experienced with apex and triggers and wondering if you can explain what happens in this trigger during its lifetime.  I understand that trigger is bulkified and will handle 200 records each time but im wondering how the variables are changed during this process and how the trigger handles calling the future method only once for every 200 records. 

Damien_Damien_

 

 

  for(Contact contact : trigger.old)
  { 
    emailissue = contact.Email_Issue__c;//This store the OLD value of the LAST executing object in the trigger
  }
  
  for(Contact contact : trigger.new)
  {            
    if(contact.Email_Issue__c == null && emailissue != null)//This goes through the new of EVERY activating object.  If ANY of these Email_Issue__c are null and last one in the list wasn't null then it goes into your if and sets that value to true.  Basically these 2 commented lines I have here are where I believe your messups were.

Ditch the first loop and replace your if statement with:

if (contact.Email_Issue__c == null && Trigger.oldMap.get(contact.Id).Email_Issue__c != null)

 

 

nelrib88nelrib88

Thanks so much i will try this.

nelrib88nelrib88

i actually left out a couple lines that are in my trigger code.  I set the value of emailissue to null just before setting it to the old value.

 

Does this mean that what you asked me to change won't really make a differnce or do you still believe my problem is in that loop.  I havent yet been able to test the change and feel like the boolean variable 'changed' is more likely to be causing the problem and that i should maybe have a better way of checking to see if a contacts email issue was changed.

 

thanks

 

i posted full code below of my trigger.

 

trigger InvalidEmailContactTrigger on Contact (before update)
{
    private String emailissue {get; set;}     private String session {get; set;}     private String server {get; set;}     private String lastName {get; set;}     private Boolean changed = false;     Map<Id,String> contactsToUpdate = new Map<Id, String>();          if(Trigger.isBefore)     {         emailissue = null;         for(Contact contact : trigger.old)         {              emailissue = contact.Email_Issue__c;         }                  for(Contact contact : trigger.new)         {                         if(contact.Email_Issue__c == null && emailissue != null)             {                   contactsToUpdate.put(contact.Id, emailissue);                 session = contact.session_id__c;                 server = contact.server_url__c;                 lastName = contact.lastName;                 changed = true;             }          }         if(lastName == 'ApexRunTestIssue'){             EmailIssueRemover.test(contactsToUpdate);         } else {                  if(changed){                 EmailIssueRemover.removeEmailIssue(contactsToUpdate, session, server);             }         }     } }
Damien_Damien_

What I posted will definitely help your trigger.  There may be other issues we find after, but I can guarantee that is one issue your trigger has.

nelrib88nelrib88

i only asked because the trigger does work fine when updated directly through a contacts page and also through bulk uploader just doesnt seem to work in the batch process since the future method is being called unnecessarily.

 

thanks again

Damien_Damien_

I think you have only been getting lucky with it working with the bulk uploader.

Starz26Starz26

So heres the deal on your trigger (and yes you should remove the trigger.old loop and replace it with the reccomendation of the previous poster:

 

//If a Before Trigger
if(Trigger.isBefore)
    {
        //You set the initial value to NULL
        emailissue = null;
        //Here you loop through ALL 200 records in the trigger, the value of the last record - Email_Issue__c is saved as the comparison. The problem is this is not performing the trigger on each individual but looping through all 200 and the last records ends up being the value set.
        for(Contact contact : trigger.old)
        { 
            emailissue = contact.Email_Issue__c;
        }
        
        //Now you loop through ALL 200 records in the trigger and for each one you compare the value of Email_Issue__c to the LAST RECORDS VALUE of emailissue (see above). Thus if the last record processed is = null and ANY record in the trigger has an Email_Issue__c == NULL you set the value of changed. In addition, the value of CHANGED is not reset on a per record basis so I would simply check for !contactsToUpdate.isEmpty() and drop the changed flag
        for(Contact contact : trigger.new)
        {            
            if(contact.Email_Issue__c == null && emailissue != null)
            {  
                contactsToUpdate.put(contact.Id, emailissue);
                session = contact.session_id__c;
                server = contact.server_url__c;
                lastName = contact.lastName;
                changed = true;
            } 
        }
        if(lastName == 'ApexRunTestIssue'){
            EmailIssueRemover.test(contactsToUpdate);
        } else {     
            if(changed){
                EmailIssueRemover.removeEmailIssue(contactsToUpdate, session, server);
            }
        }
    }
}

 

This was selected as the best answer
nelrib88nelrib88

Thanks to both of you for all the help this is the explanation i was looking for.