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
NY70NY70 

Trigger doesn't process all records at once

I have a trigger on a custom object which depending on some criteria; it is supposed to create a list of emails to send.  I have created the trigger and it works fine.  Code coverage is 100%.  I’m ready to deploy it to production.

 

I’m calling a future method to process the records asynchronously.  I was expecting to be able to pass a list of account Ids created from my 2000 records to the future method to process but the trigger gives me 200 records at a time.  By the time the test method is done running, I’m very close to the limit of Number of future calls I can make.  What I really wanted and expected was for the future method to be called once and for all the processing to happen there.

 

Why does the trigger or apex automatically break up the 2000 records into bunches of 200 and process them separately? 

 

10:18:45.395|METHOD_EXIT|[7]|testAWAfterInsert

10:18:45.395|METHOD_ENTRY|[69]|System.debug(ANY)

10:18:45.395|USER_DEBUG|[69]|DEBUG|********** TEST 5 **********

10:18:47.357|METHOD_EXIT|[84]|system.Test.startTest()

10:18:47.358|DML_BEGIN|[85]|Op:Insert|Type:Actuals_Weekly__c|Rows:2000

10:18:48.160|USER_DEBUG|[19]|DEBUG|********** newAWs.size = 200

Number of future calls: 1 out of 10

...

10:18:55.859|USER_DEBUG|[19]|DEBUG|********** newAWs.size = 200

Number of future calls: 10 out of 10 ******* CLOSE TO LIMIT

 

Here is my trigger:

trigger AWTriggers on Actuals_Weekly__c (after delete, after insert, after undelete,after update,

                                         before delete, before insert, before update) {

   

    if(trigger.isAfter){

                      if(trigger.isInsert){

                                              AWAfterInsert myAfterInsert = new AWAfterInsert(Trigger.new);

                        }

                        if(trigger.isUpdate){}

                        if(trigger.isDelete){}

                        if(trigger.isUndelete){}

    }

   

    if(trigger.isBefore){

                        if(trigger.isDelete){}

                        if(trigger.isInsert){}

                        if(trigger.isUpdate){}

    }

}

Here is my class:

public with sharing class AWAfterInsert {

 

                        public AWAfterInsert(Actuals_Weekly__c[] newAWs){

                                                NotOrderedMGA14(newAWs);

                        }

                        public static void NotOrderedMGA14(Actuals_Weekly__c[] newAWs){

                                                System.debug('********** newAWs.size = ' + newAWs.size());

                                               

                                                Set<Id> accountIds = new Set<Id>();

                                                for(Actuals_Weekly__c aw : newAWs){

                                                                        accountIds.add(aw.Account__c);

                                                }

 

                                                AWGlobalUtils.asyncNotOrderedMGA14(accountIds);

                        }

}

Here is my future method:

global class AWGlobalUtils {

 @future public static void asyncNotOrderedMGA14(Set<Id> accountIds){

  System.debug('****************************** accountIds.size = ' + accountIds.size());

  list<Actuals_Weekly__c> awl = Database.query('Select Id, Procedure__c, Read_Date__c, Account__c, Account__r.Name, Account__r.OwnerId From Actuals_Weekly__c Where Procedure__c = \'MOLECULAR GRADING ASSAY\' And Read_Date__c < ' + getTargetDate() + ' And Account__c in : accountIds');

  System.debug('********** awl.size = ' + awl.size());

  Map<Id,Actuals_Weekly__c> am = new Map<Id,Actuals_Weekly__c>();

  if(!awl.isEmpty()) {

   for(Actuals_Weekly__c aw2 : awl){

    am.put(aw2.Account__c,aw2);

   }

  }

  System.debug('********** am.size = ' + am.size());

  // Send an email to each account owner

  list<Messaging.SingleEmailMessage> el = new list<Messaging.SingleEmailMessage>();

  for(Actuals_Weekly__c aw3 : am.values()){

   Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();

   mail.setTargetObjectId(aw3.Account__r.OwnerId);

   mail.setSaveAsActivity(false);

   mail.setPlainTextBody('Your account: ' + aw3.Account__r.Name +' has not ordered MGA in the past 14 days.');

   mail.setHtmlBody('Your account:<b> ' + aw3.Account__r.Name +' </b>has not ordered MGA in the past 14 days.<p>'+

   ' To view the account <a href=https://cs1.salesforce.com/' + aw3.Account__r.Id + '>click here</a>');

   el.add(mail);

  }

  System.debug('********** el.size = ' + el.size());

  if(!el.isEmpty()){

   Messaging.sendEmail(el);

  }

 }

}

Here is my test method:

@isTest

private class testAWAfterInsert {

 static testMethod void AccountHasOrderedBulkTesting2(){

   System.debug('********** TEST 5 **********');

   List<Account> testAL = new List<Account>();

   for(integer i=0; i<200; i++){

    testAL.add(new Account(Name = 'Test Account ' + i, Account_Classification__c = 'Doctor'));

   }

   insert testAL;

   Date d2 = date.newinstance(2011, 1, 17);

   List<Actuals_Weekly__c> testAWL2 = new List<Actuals_Weekly__c>();

   for(Account a : testAL){

    for(integer i=0; i<10; i++){

     testAWL2.add(new Actuals_Weekly__c(Account__c = a.Id, Read_Date__c = d2, Procedure__c = 'MOLECULAR GRADING ASSAY'));

    }

   }

   test.startTest();

   insert testAWL2;

   test.stopTest();

 }

} 

Jeremy-KraybillJeremy-Kraybill

Why does the trigger or apex automatically break up the 2000 records into bunches of 200 and process them separately? 

 

It's under-documented, but that's what the platform does for triggers (and batch apex). AFAIK there is no way to force a larger batch size than 200.

 

I think it could be possible to do something tricky with static data to queue up the changes you want to send to the future method, but I haven't tested it. Static variables are request-scoped, so theoretically you maybe could do something like have a before insert trigger that passes its records to a class that adds them to a growing static List variable, and then an after insert trigger which calls a different method on that same class which then passes the entire List to the future method. Not sure that would work though, there's a lot of ways that approach could fail and it's a bit of a hack anyway. You'd also have to watch how many emails your code is sending, as depending on your license size and the number of bulk records you process, you could run into email governor limits too.

 

Let us know what you end up doing to resolve this.

 

Jeremy Kraybill

ca_peterson_oldca_peterson_old

Can I ask why you're using @future here? I don't see a reasont to not just send the emails from within the trigger.

NY70NY70

hi ca_peterson

 

becuase since the trigger handles the 2000 records in bunches of 200 records, I would run into the same issue with Email Invocations instead.  So I thought that if I used a future call, all my records would be processed at once and I would only use one Email Invocation, but found out that it wasn't so.

 

Number of Email Invocations: 10 out of 10 ******* CLOSE TO LIMIT

 

I will rewrite the trigger to not call the future method and just do everything inside the trigger.  My  chances of reaching the Email Invocations limit is much less than the future call limit by far.