Bulkification Apex - Troppe query SOQL: 101

Oct 05 2020

Ho una classe APEX che ho raggruppato, ma continua a darmi problemi con lotti di 200 record. Speravo di ottenere assistenza per rendere l'APICE più efficiente, qualsiasi assistenza / direzione sarebbe stata molto apprezzata.

Ho un generatore di processi che attiva una classe Apex sull'oggetto principale quando viene soddisfatta una delle seguenti condizioni:

        ISCHANGED([Lead].MobilePhone),
        ISCHANGED([Lead].Phone),
        ISCHANGED([Lead].Home_Phone__c),
        ISCHANGED([Lead].Alternate_Phone__c),
        ISCHANGED([Lead].Alternate_Phone_2__c),
        ISNEW()

Il Process Builder passa il [Lead] .Id alla classe APEX.

public class UpdatePhoneTrimFields {

    @InvocableMethod
    public static void invocableTrimPhoneNumbers(List<String> ids){

        String objectType = ids[0];

        if(objectType.startsWithIgnoreCase('00Q')){

            updateBulkifiedLeadPhoneTrimFields(ids); 
        }
        else if(objectType.startsWithIgnoreCase('003')){

            updateBulkifiedContactPhoneTrimFields(ids);
        } 
        else if(objectType.startsWithIgnoreCase('001')){

            updateBulkifiedAccountPhoneTrimFields(ids);
        } 
        else {

            system.debug('Unknown Record Type - Cannot Proceed');
        }

    }

    private static Decimal normalizePhoneNumber(String rawPhoneNumber){
        try{
            if(rawPhoneNumber != null){
                return Decimal.ValueOf(rawPhoneNumber.replaceAll('[^0-9]', ''));
            }
        } catch (Exception e){

            System.debug('An Unexpected error occurred trimming a phone number: ' + e.getMessage());
        }
        return null;
    }

    private static void updateBulkifiedLeadPhoneTrimFields(List<String> ids){
        
        system.debug('Attempting to Normalize the Phone numbers for a list of ' + ids.size() + ' leads.');

        list<Lead> leadsToUpdate = new List<lead>(); 

        for(Id recordId: ids){
            
            String stringRecordId = Id.ValueOf(recordId);

            if(stringRecordId.startsWithIgnoreCase('00Q')){
                try {
                    Lead l = [SELECT Phone, MobilePhone, Home_Phone__c, Alternate_Phone__c, Alternate_Phone_2__c, Phone_Trim__c, MobilePhone_Trim__c, Home_Phone_Trim__c, Alternate_Phone_Trim__c, Alternate_Phone_2_Trim__c FROM Lead WHERE Id =: recordId];

                    l.Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Phone);
                    l.MobilePhone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.MobilePhone);
                    l.Home_Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Home_Phone__c);
                    l.Alternate_Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Alternate_Phone__c);
                    l.Alternate_Phone_2_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Alternate_Phone_2__c);

                    system.debug('Adding Lead to List to Normalize: '+ l);
                    leadsToUpdate.add(l);
                }catch(Exception e){
                    System.debug('An Unexpected error occurred normalizing Lead Phone Fields: ' + e.getMessage());
                }
            } else {
                System.debug('Cannot Normalize Phone for this record: ' + recordId + '. Not a Lead');
            }   
        }

        try {
            system.debug('Updading a list of Leads with ' + leadsToUpdate.size() + ' records');
            system.debug(leadsToUpdate);
            update leadsToUpdate; 
        } catch (Exception e) {
            System.debug('An Unexpected error occurred normalizing Lead Phone Fields: ' + e.getMessage());
        }
    }

    private static void updateBulkifiedContactPhoneTrimFields(List<String> ids){

        system.debug('Attempting to Normalize the Phone numbers for a list of ' + ids.size() + ' Contacts.');

        list<Contact> contactsToUpdate = new List<Contact>();

        for(Id recordId: ids){

            String stringRecordId = Id.ValueOf(recordId);

            if(stringRecordId.startsWithIgnoreCase('003')){
                try{
                    Contact c = [SELECT Phone, MobilePhone, HomePhone, OtherPhone, Phone_Trim__c, MobilePhone_Trim__c, HomePhone_Trim__c, OtherPhone_Trim__c FROM Contact Where Id =: recordId];

                    c.Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(c.Phone);
                    c.MobilePhone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(c.MobilePhone);  
                    c.HomePhone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(c.HomePhone);
                    c.OtherPhone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(c.OtherPhone);

                    system.debug('Adding Contact to List to Normalize: '+ c);
                    contactsToUpdate.add(c);

                }catch(Exception e){
                    System.debug('An Unexpected error occurred normalizing Lead Phone Fields: ' + e.getMessage());
                }
            }else {
                System.debug('Cannot Normalize Phone for this record: ' + recordId + '. Not a Contact');
            }
        }

        try {
            system.debug('Updating List of Contacts with ' + contactsToUpdate.size() + ' records');
            system.debug(contactsToUpdate);
            update contactsToUpdate;
        } catch (Exception e) {
            System.debug('An Unexpected error occurred trimming Contact Phone Fields: ' + e.getMessage());
        }
    }

    private static void updateBulkifiedAccountPhoneTrimFields(List<String> ids){

        system.debug('Attempting to Normalize the Phone numbers for a list of ' + ids.size() + ' Accounts.');

        list<Account> accountsToUpdate = new List<Account>();

        for(Id recordId: ids){

            String stringRecordId = Id.ValueOf(recordId);

            if(stringRecordId.startsWithIgnoreCase('001')){
                try{
                    Account a = [SELECT Phone, Alternate_Phone__c, Alternate_Phone_2__c, Phone_Trim__c, Alternate_Phone_Trim__c, Alternate_Phone_2_Trim__c FROM Account WHERE Id =: recordId];
            
                    a.Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(a.Phone);
                    a.Alternate_Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(a.Alternate_Phone__c);
                    a.Alternate_Phone_2_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(a.Alternate_Phone_2__c);
            
                    system.debug('Adding Account to List to Normalize: '+ a);
                    accountsToUpdate.add(a);
                }catch(Exception e) {
                    System.debug('An Unexpected error occurred normalizing Account Phone Fields: ' + e.getMessage());
                }
            } else {
                System.debug('Cannot Normalize Phone for this record: ' + recordId + '. Not an Account');
            }
        }

        try {
            system.debug('Updating List of Accounts with ' + accountsToUpdate.size() + ' records');
            system.debug(accountsToUpdate);
            update accountsToUpdate;
        } catch (Exception e)   {
            System.debug('An Unexpected error occurred trimming Account Phone Fields: ' + e.getMessage());
        }      
    }

}
  1. Questa classe Apex può essere attivata da tre process builder relativi all'oggetto lead / account / contatto. Quindi il generatore di processi chiama un @invocablemethod che determina il tipo di oggetto. In base al tipo di oggetto, chiamerà quindi un metodo privato.
  2. Questo metodo privato è updateBulkifiedLeadPhoneTrimFields. Passa attraverso l'array / elenco di record: cerca le informazioni appropriate, aggiorna il record e lo invia a un nuovo array. Una volta terminato il ciclo, aggiorniamo insieme l'elenco dei record.
  3. Gli aggiornamenti che vengono eseguiti sono gli stessi in tutti i metodi: questo si aggiorna ai campi phonetrim (normalizza i campi phone). Il metodo chiamato è normalizePhoneNumber

updateBulkifiedLeadPhoneTrimFields

private static void updateBulkifiedLeadPhoneTrimFields(List<String> ids){

        system.debug('Attempting to Normalize the Phone numbers for a list of ' + ids.size() + ' leads.');

        list<Lead> leadsToUpdate = new List<lead>(); 

        for(Id recordId: ids){

            String stringRecordId = Id.ValueOf(recordId);

            if(stringRecordId.startsWithIgnoreCase('00Q')){
                try {
                    Lead l = [SELECT Phone, MobilePhone, Home_Phone__c, Alternate_Phone__c, Alternate_Phone_2__c, Phone_Trim__c, MobilePhone_Trim__c, Home_Phone_Trim__c, Alternate_Phone_Trim__c, Alternate_Phone_2_Trim__c FROM Lead WHERE Id =: recordId];

                    l.Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Phone);
                    l.MobilePhone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.MobilePhone);
                    l.Home_Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Home_Phone__c);
                    l.Alternate_Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Alternate_Phone__c);
                    l.Alternate_Phone_2_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Alternate_Phone_2__c);

                    system.debug('Adding Lead to List to Normalize: '+ l);
                    leadsToUpdate.add(l);
                }catch(Exception e){
                    System.debug('An Unexpected error occurred normalizing Lead Phone Fields: ' + e.getMessage());
                }
            } else {
                System.debug('Cannot Normalize Phone for this record: ' + recordId + '. Not a Lead');
            }   
        }

        try {
            system.debug('Updading a list of Leads with ' + leadsToUpdate.size() + ' records');
            system.debug(leadsToUpdate);
            update leadsToUpdate; 
        } catch (Exception e) {
            System.debug('An Unexpected error occurred normalizing Lead Phone Fields: ' + e.getMessage());
        }
    }

normalizePhoneNumber

    private static Decimal normalizePhoneNumber(String rawPhoneNumber){
        try{
            if(rawPhoneNumber != null){
                return Decimal.ValueOf(rawPhoneNumber.replaceAll('[^0-9]', ''));
            }
        } catch (Exception e){

            System.debug('An Unexpected error occurred trimming a phone number: ' + e.getMessage());
        }
        return null;
    }

Quando si esegue in batch da 200 ricevo il seguente errore: An Apex error occurred: System.LimitException: Too many SOQL queries: 101 ---

Domande)

  1. C'è un modo per renderlo più efficiente? Se é cosi, come? Il mio obiettivo finale sarebbe che questo sia in grado di funzionare in batch di 200 record.

Aggiornare

Ho apportato la seguente modifica e sembra che abbia funzionato. Qualche consiglio su ulteriori aggiornamenti?

        
        system.debug('Attempting to Normalize the Phone numbers for a list of ' + ids.size() + ' leads.');

            List<Lead> leadsToUpdate = [SELECT Id, Phone, MobilePhone, Home_Phone__c, Alternate_Phone__c, Alternate_Phone_2__c, Phone_Trim__c, MobilePhone_Trim__c, Home_Phone_Trim__c, Alternate_Phone_Trim__c, Alternate_Phone_2_Trim__c FROM Lead WHERE Id =: ids]; 

            for(Lead l: leadsToUpdate){
                try{
                    l.Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Phone);
                    l.MobilePhone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.MobilePhone);
                    l.Home_Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Home_Phone__c);
                    l.Alternate_Phone_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Alternate_Phone__c);
                    l.Alternate_Phone_2_Trim__c = UpdatePhoneTrimFields.normalizePhoneNumber(l.Alternate_Phone_2__c);
                } catch(Exception e){
                    System.debug('An Unexpected error occurred normalizing Lead with Id: ' + l.Id + '. Error' + e.getMessage());
                }
            }
            system.debug('Updading a list of Leads with ' + leadsToUpdate.size() + ' records');
            system.debug(leadsToUpdate);
            update leadsToUpdate; 
    }

Risposte

3 DerekF Oct 05 2020 at 20:09

Il problema è che hai query all'interno di un ciclo.

Nel tuo tentativo di raggruppamento, vediamo questo

for(Id recordId: ids){
    String stringRecordId = Id.ValueOf(recordId);

    if(stringRecordId.startsWithIgnoreCase('00Q')){
        try {
            // This query is inside of a loop
            // That's a problem
            Lead l = [SELECT Phone, MobilePhone, Home_Phone__c, Alternate_Phone__c, Alternate_Phone_2__c, Phone_Trim__c, MobilePhone_Trim__c, Home_Phone_Trim__c, Alternate_Phone_Trim__c, Alternate_Phone_2_Trim__c FROM Lead WHERE Id =: recordId];
   // ...

Poiché hai un solo filtro nella tua query, è facile uscire dal ciclo usando l'approccio standard iterate-gather-query.

// First, set up a collection to hold the data you want to query against
Set<Id> recordIds = new Set<Id>();

// Iterate over your records to gather the data
for(SomeObject__c someObj :trigger.new){
    // gather your data
    recordIds.add(someObj.Relationship_Field__c);
}

// Query and store for later use
Map<Id, SObject> sobjMap = new Map<Id, SObject>([SELECT Id, Field1__c, Field2__c FROM OtherObject__c WHERE Id IN :recordIds);

// Iterate over the records again
// This time, you can pull the related data from the map you made earlier
for(SomeObject__c someObj :trigger.new){
    OtherObject__c relatedRecord = sobjMap.get(someObj.Relationship_Field__c);

    // do other work
}

Probabilmente non è necessario affrontare così tanti problemi, dato che stai lavorando solo con 1 SObject alla volta, non dovrebbe esserci nulla che ti impedisca di alimentare semplicemente un ciclo for con una query che utilizza idsdirettamente il tuo elenco .

for(Lead myLead :[SELECT Id, Field2__c, Field3__c FROM Lead WHERE Id IN :ids]){
    // do work
}

Oltre a quello...

  • potresti prendere in considerazione l'utilizzo del getSObjectType()metodo della Idclasse per rendere più leggibile il rilevamento di SObject
    • ad esempio SObjectType sobjType = ((Id)ids[0]).getSObjectType();eif(sobjType == Lead.SObjectType)
  • Ingoiare le eccezioni è generalmente una cosa negativa. Rende più difficile rilevare quando si verifica un problema e rende più difficile eseguire il debug del problema una volta che si sa di averne uno. Considera l'idea di eliminare tutte le tue dichiarazioni try / catch