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
WhyserWhyser 

Apex Trigger Firing Twice - How to Prevent?

I have an Apex trigger that writes "Lead Status History" to a custom object I've made called "Lead_Status_History__c". It fires whenever the lead status changes on a lead. The problem I am having is that the trigger is firing twice on a specific Lead Status.
 
I believe the reason why it is firing twice on this specific lead status is because I have a workflow rule set up on the lead. When the lead status is changed to that specific status, it performs a field update on a date field on the lead, and I believe that this field update is causing my trigger to fire again.
 
To rehash the order of execution when it comes to triggers, I'll highlight the steps that are relevant to me:
 

1. The original record loads from the database or initializes for an insert operation
2. The new values load from the incoming request and overwrite the old values in the record buffer. The old values are saved in the old context variable for update triggers.
3. Before triggers run.
...
5. The record is saved to the database, but the record is not committed.
6. After triggers run.
...
8. Workflow rules execute. If field updates are specified, the record updates again, and before and after triggers for the update fire.
...
10. All data manipulation operations are committed to the database.


I believe step 8 is causing my trigger to fire again. Is there anyway to prevent triggers from doing a repeat action if step 8 causes your trigger to fire again?

Here is my apex trigger code:

Code:
trigger LeadStatusHistoryTrigger on Lead (before update) 
{
 // Declare a list of Lead_Status_History__c to be insertedin Salesforce
 List<Lead_Status_History__c> LeadStatusHistoryObjects = new List<Lead_Status_History__c>();
 
 // If the trigger is an update trigger insert all the new status changes
 if ( Trigger.isUpdate )
 {
  for ( Lead l_new: Trigger.new )
  {
    // Check to see if the status of the old and new lead values are the same.
    // If they are different then we create a new entry in the lead_status_history custom object
    if ( l_new.Status != Trigger.oldMap.get( l_new.Id ).Status )
    {
     LeadStatusHistoryObjects.add( new Lead_Status_History__c ( Lead__c = l_new.Id, Lead_Status_New__c = l_new.Status, Lead_Status_Old__c = Trigger.oldMap.get( l_new.Id ).Status ) ); 
    }
  }
  
  // After all the new Lead Status History items have been added, now insert them into Salesforce
  try
  {
   insert LeadStatusHistoryObjects;
  }
  catch (DmlException de)
  {
   for ( integer i = 0; i < de.getNumDml(); i++ )
   {
    System.debug('LeadStatusHistoryTrigger Error Inserting Lead Status History Objects: ' + de.getDmlMessage(i));
   } 
  }
  
 }
}


 

Best Answer chosen by Admin (Salesforce Developers) 
SFHackSFHack

I believe I got this worked out.  Further testing on Monday will tell for sure.

 

I added a read-only checkbox field that's not visible on any layouts.  I created a workflow that tests for the same conditions that my trigger tests for.  It fires a new field update that sets the value of the new checkbox to true.  In my after update trigger I can just check the value of that field on newScreen (the object from Trigger.new).  It is false the first time the trigger fires, true the second time after field updates have been applied.  There was no need to query the database.

 

What I still need to test is to make sure the emails are only sent when the overall transaction succeeds.  I just have to figure out how to test that.

 

Thanks for the help.  I think it was just the back and forth I needed.

All Answers

RickyGRickyG
whyser -

I assume you already thought about checking for the conditions that would cause that workflow field update in a conditional statement for the trigger, right? 

Would another way to get around this be to add a field to the object to act as a flag. The flag could be set by the workflow to positive, checked in the trigger, and then set to negative outside of the conditional check in the trigger.  It's a kludge, but it might work.

Hope this helps.
WhyserWhyser
Hi RickyG,
 
Thanks for the reply
 
I've thought about either checking the conditions in the Apex Trigger or setting a flag, but before trying them i would like to clarify something.
 
In the order of execution, it states in step 5
 
"The record is saved to the database, but the record is not committed"
 
So what I'm wondering is this: if the record is saved but not committed, and if I query the database, would I get the saved data or the original data since it was not committed yet? The reason why I'm asking is that even if I went with this "flag" idea, I need to know that if I set a flag in the lead object, would I be able to query for that data since it was saved yet not committed?
 
 
RickyGRickyG
Dang, that is not only a good question, but one close to my data-integrity loving heart.

*** On second thought, why not just look at the trigger.new array, rather than requerying the database?  ***

My guess would be that the change would be reflected in the record, since that would be in the same session, and normally that session context would trump the saved version.  But the best course of action, of course, would be to try it out.

If you do, would you be gracious enough to post the results?

Thanks, hope this helps.


Message Edited by RickyG on 11-17-2008 03:24 PM
WhyserWhyser
Hey RickyG,
 
After some testing, here are my results.
 
This is what's been happening originally (and hence my problem):
Let's say that the Lead Status was editted so that it would fire off a workflow rule that would cause a field update back to the lead.
  1. I run my Apex trigger (before update) on leads
  2. Checks the difference between Trigger.New and Trigger.Old Lead.Status values, Lead Status was found to be different, so an entry is added into the Lead Status History List.
  3. Insert new values into Lead Status History table, saved, and not committed.
  4. Trigger.New Lead values are also "saved" to the database, but not committed.
  5. No trigger handling on after update, so the order of execution moves to the workflow rule, and thus it executes the field update.
  6. Field update writes a value on the lead, and is "saved" (not committed), and this update re-runs my Apex trigger.
  7. Trigger checks the difference between Trigger.New and Trigger.Old Lead Status values, Lead Status is STILL found to be different, so another entry is added into the Lead Status History List in which it is inserted into the Database, thus duplicating the data.

It seems that Trigger.New and Trigger.Old retain the same data throughout the "order of execution".

So what I have done for my test is, after I have checked the condition for whether the Lead Status between Trigger.New and Trigger.Old are different, I immediately query the database. Here's why:

- if this was done on the before update, no records have been saved yet, so when I query the database, I get the original data back
- if this was done after a workflow rule ran a field update which caused the trigger to run again, I would get the "saved" data back.

Using this information, I can now compare the queried data to Trigger.New data. Before the record is saved, the queried data and Trigger.New should be different, but after the record is saved, queried data should be the same as Trigger.New data.

Here is my new code (bolded part shows the new code, and it works now)

Code:
trigger LeadStatusHistoryTrigger on Lead (before update) 
{
 // Declare a list of Lead_Status_History__c to be insertedin Salesforce
 List<Lead_Status_History__c> LeadStatusHistoryObjects = new List<Lead_Status_History__c>();
 
 // If the trigger is an update trigger insert all the new status changes
 if ( Trigger.isUpdate )
 {
  for ( Lead l_new: Trigger.new )
  {
    // Check to see if the status of the old and new lead values are the same.
    // If they are different then we create a new entry in the lead_status_history custom object
    if ( l_new.Status != Trigger.oldMap.get( l_new.Id ).Status )
    {
// Query the database for the lead status. Before save will return original data, after save (as a result of a field update re-triggering this trigger) will return the saved data Lead l = [Select Id, Status from Lead Where Id = :l_new.Id]; if( l.Status != l_new.Status ) LeadStatusHistoryObjects.add( new Lead_Status_History__c ( Lead__c = l_new.Id, Lead_Status_New__c = l_new.Status, Lead_Status_Old__c = Trigger.oldMap.get( l_new.Id ).Status ) ); } } // After all the new Lead Status History items have been added, now insert them into Salesforce try { insert LeadStatusHistoryObjects; } catch (DmlException de) { for ( integer i = 0; i < de.getNumDml(); i++ ) { System.debug('LeadStatusHistoryTrigger Error Inserting Lead Status History Objects: ' + de.getDmlMessage(i)); } } } }


And if that is the case, your "flag" idea would have worked as well.

I hope this was informative for anyone trying to figure this out. 

RickyGRickyG
Whyser -

Ah ha!  It appears that the session context does take precedence - i.e., the uncommitted save is still recognized by your session, as it should be.

Thank you so much for your followup - great board citizenship!  If only everyone were so helpful! :smileyhappy:


Message Edited by RickyG on 11-18-2008 08:28 AM
SFHackSFHack

I'm having almost exactly the same problem except that it's with an after update trigger on a custom object.  However, the solution decribed above doesn't work for me.  oldMap always shows the original values.  Querying the database the second time the trigger fires also returns the original values.  The code in my trigger is not modifying any data, it's sending an email.  There are field updates that cause my trigger to fire a second time.

 

Any suggestions at all would be very much appreciated.  I'm relatively new at working with the SalesForce platform.

WhyserWhyser
Could we see your trigger code?
SFHackSFHack

Here's the code for the trigger (I've already removed the db query that didn't have any effect):

 

 

Code:
trigger NotifyPreAdmissionScreenUpdated on Pre_Admission_Screen__c (after update) {

/*
Notify Program Services when a screen that wasn't previously marked as too impaired, etc.
that is marked that way now.
*/

if (Trigger.isAfter && Trigger.isUpdate) {
// To hold the messages to all members of Program Services AccountTeam role at the facility
List<Messaging.SingleEmailMessage> messages = new List<Messaging.SingleEmailMessage>();
for (Pre_Admission_Screen__c newScreen : Trigger.new) {
Pre_Admission_Screen__c oldScreen = Trigger.oldMap.get(newScreen.Id);

// Compare new values with old. Only fire if too impaired now but not before.
if (NotificationHelper.isScreenTooImpaired(newScreen) && !NotificationHelper.isScreenTooImpaired(oldScreen)) {
List<Messaging.SingleEmailMessage> newMessages = NotificationHelper.getTooImpairedNotifications(newScreen);
if (newMessages.size() > 0)
messages.addAll(newMessages);
}
}
if (messages.size() > 0)
Messaging.sendEmail(messages);
}
}
Here is a sequence of events from the debug log after running a test that would cause this trigger to send the emails:
Beginning preventChangePASRehabUnit (before update trigger)
Beginning setPreAdmissionScreenUsers (before update trigger)
Beginning preventInvalidOwner (before update trigger)
Beginning setPreAdmissionScreenRehabUnitOwner (before update trigger)
Beginning Pre_Admission_Screen Validation Rule Evaluation
Beginning NotifyPreAdmissionScreenUpdated (after update trigger)
Beginning Workflow Evaluation
Beginning preventChangePASRehabUnit (before update trigger)
Beginning setPreAdmissionScreenUsers (before update trigger)
Beginning preventInvalidOwner (before update trigger)
Beginning setPreAdmissionScreenRehabUnitOwner (before update trigger)
Beginning NotifyPreAdmissionScreenUpdated (after update trigger)
WhyserWhyser

So is this what you tried doing, according to my code above?

 

trigger NotifyPreAdmissionScreenUpdated on Pre_Admission_Screen__c (after update) { /* Notify Program Services when a screen that wasn't previously marked as too impaired, etc. that is marked that way now. */ if (Trigger.isAfter && Trigger.isUpdate) { // To hold the messages to all members of Program Services AccountTeam role at the facility List<Messaging.SingleEmailMessage> messages = new List<Messaging.SingleEmailMessage>(); for (Pre_Admission_Screen__c newScreen : Trigger.new) { Pre_Admission_Screen__c oldScreen = Trigger.oldMap.get(newScreen.Id); // Compare new values with old. Only fire if too impaired now but not before. if (NotificationHelper.isScreenTooImpaired(newScreen) && !NotificationHelper.isScreenTooImpaired(oldScreen)) { Pre_Admission_Screen__c pas = [select fields from Pre_Admission_Screen__c where id = :newscreen.Id]; if (NotificationHelper.isScreenTooImpaired(newScreen) && !NotificationHelper.isScreenTooImpaired(pas) { List<Messaging.SingleEmailMessage> newMessages = NotificationHelper.getTooImpairedNotifications(newScreen); if (newMessages.size() > 0) messages.addAll(newMessages); } } } if (messages.size() > 0) Messaging.sendEmail(messages); } }

 

where fields are whatever fields are needed to be able to run the NotificationHelper.isScreenTooImpaired() function.

 

Another thing you can do to be sure, is to have some debug statement to show you what's being returned.

 

So right after the query statement that I put in bold, you can put in:

 

System.debug( '\n\n' + newScreen + '\n\n' + oldScreen + '\n\n' + pas );

 

If you are using Eclipse or whatever tool you are when running tests, get the debug log and compare all three variables to see if there are any differences.

 

I am hoping that the oldScreen and pas objects are different and that pas and newScreen are the same on the second firing of the trigger.

 

I know you said you tried this already, but I just want to make sure.

 

Message Edited by Whyser on 02-20-2009 02:24 PM
SFHackSFHack

Thanks, that's exactly what I had done.  I had also put debug statements in the isScreenTooImpaired method.  pas had exactly the same values as oldScreen.  newScreen had the updated values.

WhyserWhyser

Hmm.. that's very strange behavior.

 

I've now worked with APEX enough (not VisualForce though), to be quite confident in that when changes are "saved" to a database, when you query the database, you retrieve the "saved" data.

 

And if there any point in which the APEX code fails, all the changes that were "saved" to Salesforce becomes undone, unless you specifically indicate a database commit at some point in the code.

 

So going by the execution order, by the time your after trigger runs for the first time, your "new" data SHOULD have been already saved to the database, meaning that my object pas should already be the same as the newScreen object upon the first pass (and which would also result in the changes I made to your code useless). The fact that you say that pas is still the same as oldScreen baffles me.

 

The only way those changes we made to the code would work is if your trigger is fired before update. Is it possible to change the trigger to fire before update?

SFHackSFHack

No, there's other validation that occurs that could prevent a record from being saved after a "before update" trigger fires.  I want to make sure I only send the emails when an item has been successfully saved.

 

Your description is the behavior that I would expect to see.  I certainly didn't expect the field updates to be applied AFTER the "after update" trigger.  From the debug logs that seems to be what's happening though.  I'm not sure what to try next.

WhyserWhyser

According to the order of execution, workflow field updates are supposed to occur AFTER the 1st run of the after update trigger. After that, any before and after triggers are applied as a result of the field update(s).

Whatever fields in Pre_Admission_Screen__c object were updated by the field update should be retrievable via query in your after update trigger during the 2nd run.

I guess that shouldn't completely make the code changes we made useless...  since you should be able to retrieve the data that was changed by the field update.

But then again, I'm still baffled at the fact that pas would still be the same as oldScreen, in either the 1st or 2nd run!!

I am at a loss, sorry I am unable to help!

Message Edited by Whyser on 02-20-2009 03:26 PM
SFHackSFHack

OK.  After this post of yours I decided that earlier I must have either done something wrong or was remembering the results wrong.  That was the case.  One or the other of those was true.  I added the code back in that queries the database.  Although it still isn't working correctly, the results aren't exactly what I said they were.  As would be expected by your comment pas matches newScreen both times it fires.  Now I just have to think about the problem a little differently.

 

Thanks for the help and sorry for the confusion.

WhyserWhyser

No it's fine. I'm glad that through our talking we were able to figure out what happened.

Though I think you should be able to figure it out querying the field that was changed by the field update to determine whether you should send the email or not. Perhaps it's not so simple, I'm not sure. Let us know how it turns out for you!

SFHackSFHack

I believe I got this worked out.  Further testing on Monday will tell for sure.

 

I added a read-only checkbox field that's not visible on any layouts.  I created a workflow that tests for the same conditions that my trigger tests for.  It fires a new field update that sets the value of the new checkbox to true.  In my after update trigger I can just check the value of that field on newScreen (the object from Trigger.new).  It is false the first time the trigger fires, true the second time after field updates have been applied.  There was no need to query the database.

 

What I still need to test is to make sure the emails are only sent when the overall transaction succeeds.  I just have to figure out how to test that.

 

Thanks for the help.  I think it was just the back and forth I needed.

This was selected as the best answer
cogcog

How about using a static boolean variable of some class?

Code from http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_classes_static.htm

 Now, i don't know how this is going to be in the case of batches.

public class p { 
public static boolean firstRun = true;
}

A trigger that uses this class could then selectively fail the first run of the trigger:

trigger t1 on Account (before delete, after delete, after undelete) { 
if(Trigger.isBefore){
if(Trigger.isDelete){
if(p.firstRun){
Trigger.old[0].addError('Before Account Delete Error');
p.firstRun=false;
}
}
}
}
Abeer @ Cloud3Abeer @ Cloud3

I would go for Static variable any time if that would work.

 

p.s. Other solutions that I see have SOQL query in for loop, not good if you run batch updates. Which you will need to many times.... I'm sure there would be many smart solutions to this...