You need to sign in to do that
Don't have an account?
Controlling recursive triggers - Handling errors in batches
I'm seeing a limitation in terms of controlling recursive triggers when the allOrNone database option is set to false and then the trigger causes an error to be added to a record in the batch.
To illustrate the limitation, I have used the example from the guidance for controlling recursive triggers described on the following page:
http://www.salesforce.com/docs/developer/cookbook/Content/apex_controlling_recursive_triggers.htm
I have made some minor modifications to this example. I updated the AutoCreateFollowUpOnTasks trigger to have the following modification to simulate the failure of a business rule for one record during a bulk insert:
Integer taskCount = 0;
for (Task t : Trigger.new) {
// Fake an rule failure
if (taskCount++ == 1)
{
t.addError('Test error');
}
else if (t.Create_Follow_Up_Task__c) {
Then I changed the testCreateFollowUpTasks method to have the following code:
Database.insert(tasksToCreate, false);
When I execute that test method, I can see that three tasks are inserted rather than the four that would have been inserted before my change. I expect that. But I also see that no follow-up tasks are created. I would have expected that three follow-up tasks would be created.
It seems like when the allOrNone option is set to false and a trigger causes an error to be added to a record, then the trigger is re-run with the successful records without the static variables being reset. Is that right? If so, is there anything that can be done to handle recursive triggers in a way that would account for that situation?
Your assertion is not true -- the static variables will retain their value throughout the transaction context. Please post all your code so we can assist.
My code is the same as shown on http://www.salesforce.com/docs/developer/cookbook/Content/apex_controlling_recursive_triggers.htm with the following differences:
Hmm
Do you see this executing in the debug log -- that is, you should see DML for the original three Tasks and another DML for the followup tasks? I would expect you would.
2. The static variable set by
only controls the runaway creation of followup tasks to followup tasks
3. This leads me to think that your last system assertion is not fetching the correct rows. Is there any chance you have some other trigger/workflow that modifies task subject or activity date
4. Stylistically, I always use method addDays(1) to add days to a date rather than adding integer '1'
5. I don't know why SFDC did this in their example but best practices should do DML of related records in the after trigger, not the before trigger - this way, only the SObjects that actually were successfully inserted/updated will trigger follow-on DML to other SObjects
6. Best practice in testmethod when doing Database.xxx statements is to use
Thanks for looking into this, Eric.
Regarding point #3. To eliminate the chance of issues with the SOQL, I added the following assert to my test case.
This fails with "expected: 6, actual: 3" indicating the follow-up tasks are not created.
Regarding point #1: I added some debugging code in the method that checks the static variable:
In the logs I see this (shortened for clarity):
07:39:46.598 (3598820000)|CODE_UNIT_STARTED|[EXTERNAL]|01pL000000055WQ|FollowUpTaskTester.testCreateFollowUpTasks
07:39:46.601 (3601344000)|DML_BEGIN|[21]|Op:Insert|Type:Task|Rows:4
07:39:46.740 (3740149000)|CODE_UNIT_STARTED|[EXTERNAL]|01qL00000004NEW|AutoCreateFollowUpTasks on Task trigger event BeforeInsert for [new, new, new, new]
07:39:46.745 (3745540000)|USER_DEBUG|[16]|DEBUG|hasAlreadyCreatedFollowUpTasks: false
07:39:46.751 (3751716000)|DML_BEGIN|[39]|Op:Insert|Type:Task|Rows:3
07:39:46.756 (3756236000)|CODE_UNIT_STARTED|[EXTERNAL]|01qL00000004NEW|AutoCreateFollowUpTasks on Task trigger event BeforeInsert for [new, new, new]
07:39:46.756 (3756512000)|USER_DEBUG|[16]|DEBUG|hasAlreadyCreatedFollowUpTasks: true
07:39:46.756 (3756607000)|CODE_UNIT_FINISHED|AutoCreateFollowUpTasks on Task trigger event BeforeInsert for [new, new, new]
07:39:48.275 (5275756000)|DML_END|[39]
07:39:48.276 (5276052000)|CODE_UNIT_FINISHED|AutoCreateFollowUpTasks on Task trigger event BeforeInsert for [new, new, new, new]
07:39:48.429 (5429314000)|CODE_UNIT_STARTED|[EXTERNAL]|01qL00000004NEW|AutoCreateFollowUpTasks on Task trigger event BeforeInsert for [new, new, new]
07:39:48.429 (5429625000)|USER_DEBUG|[16]|DEBUG|hasAlreadyCreatedFollowUpTasks: true
07:39:48.429 (5429716000)|CODE_UNIT_FINISHED|AutoCreateFollowUpTasks on Task trigger event BeforeInsert for [new, new, new]
07:39:48.496 (5496022000)|DML_END|[21]
07:39:48.505 (5505927000)|CODE_UNIT_FINISHED|FollowUpTaskTester.testCreateFollowUpTasks
I believe the section highlighted in red is where the trigger is being re-run with the existing static variables rather than reset static variables.
IndyR
The debug log trace you sent up to the red lines is as expected. You can see the 3 followup tasks inserted at 07:39:46.751
You can also see that hasAlreadyCreatedFollowUpTasks: true remains true once set, even at 07:39:48.429 (5429625000). This is also as expected, static variables persist for the length of the transaction (testmethod in this case).
It is not clear to me why the three followup tasks are triggered twice unless you have some workflow rule that causes the Tasks to be modified somehow (and thus the before trigger will refire) - see Apex Developers Guide - Trigger Order of Execution
It is also not clear to me why you don't have 6 tasks since there is successful DML for 3+3
I'll go back to an earlier remark which is that you should not do explicit DML in before triggers - do in after triggers; try restructuring your code this way.
Thanks for continuing to look at this, Eric.
I did have a workflow firing, but I believed it didn't have an impact on what was happening. To be sure, I repeated my test in a brand new developer edition sandbox and I received the same results as shown in the log above.
In the multitenancy white paper there's these lines:
http://www.developerforce.com/media/ForcedotcomBookLibrary/Force.com_Multitenancy_WP_101508.pdf
I'm starting to think that the platform rolls back side effects in terms of DML but not in terms of static variables.
IndyR
Hmm, this is interesting. There is no way SFDC could roll back static variables as they persist through the transaction context.
I've never run into this phenomenon - probably because I never do explicit DML in a before trigger - I use after triggers instead - which, as described above in an earlier remark, avoid the partial update issue altogther as only successfully saved records will be triggered in the after update/insert trigger.
The static variables will retain their value even across the before | after trigger "boundary".