0

After the publish of this article: https://developer.salesforce.com/blogs/2019/06/get-buildspiration-with-asynchronous-apex-triggers-in-summer-19.html

I'm trying to implement an async apex trigger on Account.

This is the trigger:

trigger AccountAsyncTrigger on AccountChangeEvent (after insert) {
    Set<String> newAccSetId = new Set<String>();
    List <Account> accToUpdate = new List <Account>();

    for (AccountChangeEvent acc_i : Trigger.New) {
        EventBus.ChangeEventHeader header = acc_i.ChangeEventHeader;
        System.debug('Received change event for ' + header.entityName +' for the ' + header.changeType + ' operation.'); 
        List<String> recordIds = acc_i.ChangeEventHeader.getRecordIds();
        newAccSetId.addAll(recordIds);
    }
    Map <Id,Account> accountsMap = new Map<Id,Account>([SELECT Id,Partner_Type__c from Account where Id IN : newAccSetId]);
    if(!accountsMap.isEmpty()){
        for(AccountChangeEvent acc_i : Trigger.New){
            Account acc = accountsMap.get(acc_i.ChangeEventHeader.getRecordIds()[0]);
            if(acc.Partner_Type__c != null){
                acc.isPartner = true;
            }
            else{     
                acc.isPartner = false;
            }
            accToUpdate.add(acc);
        }

        try {
            if(!accToUpdate.isEmpty()){
                update accToUpdate;
            }
        } catch (DmlException ex) {
            Trigger.New.get(0).addError(ex);
        }  
    }
}

i ran debug logs on "Automated Process", and tried to insert an account. For some reason, the Automated Process keeps updating the account in an infinite loop: enter image description here

I have no idea why this happens, and when i looked in the log, only "AfterInsert" (The Async Trigger),"BeforeUpdate" and "AfterUpdate" are running.

Anyone has an idea why it happens?

1

You're modifying a record, which triggers an AccountChangeEvent, which modifies a record, which triggers an AccountChangeEvent... and so on. This is expected behavior. It is unclear why you're using a change event here, since it would be more appropriate to do this in an Account.BeforeUpdate handler instead:

trigger AccountPartnerCheck on Account (before update) {
  for(Account record: Trigger.new) {
    record.IsPartner = record.Partner_Type__c != null;
  }
}

If you must use this AccountChangeEvent.AfterInsert (e.g. because of Process Builder, etc), you need to check to see if the value actually changed in order to break the infinite loop:

trigger AccountAsyncTrigger on AccountChangeEvent (after insert) {
    Set<String> newAccSetId = new Set<String>();
    for (AccountChangeEvent acc_i : Trigger.New) {
        newAccSetId.addAll(acc_i.ChangeEventHeader.getRecordIds());
    }
    Account[] accToUpdate = [
      SELECT Partner_Type__c from Account 
      where Id IN : newAccSetId and (
        (ispartner = false and partner_type__c != null) 
        or
        (ispartner = true and partner_type__c = null))];
    for(Account record: accToUpdate) {
        record.IsPartner = record.Partner_Type__c != null;
    }
    Database.update(accToUpdate, false);
}

Here, we additionally filter the query to show only records that need to be updated (i.e. the field shows IsPartner equals true but has no type, or isPartner equals false but has a type).

This will prevent the infinite loop, since we only update records that need to be modified.

  • I'm not using beforeUpdate because the field "isPartner" is not writeable + i want to move this to AccountChangeEvent to avoid hitting limits. A few questions: 1. isn't the "after insert" enables the trigger to run only when inserting a record and the event is fired? 2. Why are u using Database.update instead of just update? 3. Where are you checking if there was a change? – Dana Griff Jun 30 at 10:20
  • @DanaGriff (1) No, After Insert for the Change Event refers to any change to a record, including updates. This is the cause for the infinite loop. (2) So that as many records as possible can be updated. Using the try-catch mechanism can result in far more data loss. (3) I check to see if the flag and the field agree with each other (this is why the OR filter is there). If they do not, then I know they need to be updated. – sfdcfox Jun 30 at 10:28
  • So why do you need the "after insert" annotation? – Dana Griff Jun 30 at 11:53
  • @DanaGriff because the change event is being created. It does not refer to the record being created, but rather the event. – sfdcfox Jun 30 at 13:58
  • Got it, Thanks! – Dana Griff Jul 1 at 7:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.