Empiece a realizar un seguimiento de su progreso
Inicio de Trailhead
Inicio de Trailhead

Desencadenadores de Apex masivos

Objetivos de aprendizaje

Después de completar esta unidad, podrá:
  • Escribir desencadenadores que funcionen en colecciones de sObjects.
  • Escribir desencadenadores que realicen operaciones SOQL y DML eficientes.

Patrones de diseño de desencadenadores masivos

Los desencadenadores de Apex se han optimizado para funcionar de forma masiva. Se recomienda el uso de patrones de diseño masivos para procesar registros en desencadenadores. Mediante el uso de patrones de diseño masivos, mejora el desempeño de los desencadenadores, se consumen menos recursos de servidor y es menos probable que se superen los límites de la plataforma.

La ventaja de procesar el código de forma masiva es que este puede procesar un elevado número de registros de forma eficiente y ejecutarse dentro de los límites regulados en la Plataforma Lightning. Estos límites regulados se incluyen para garantizar que el código no controlado no monopolice los recursos de la plataforma de múltiples arrendatarios.

En las siguientes secciones se describen los métodos principales para procesar de forma masiva el código Apex en desencadenadores (realización de operaciones en todos los registros del desencadenador y aplicación simultánea de SOQL y DML a colecciones de sObjects en lugar de a sObjects individuales). Las mejores prácticas de procesamiento masivo de SOQL y DML se aplican a todo el código Apex, lo que incluye SOQL y DML en clases. Los ejemplos incluidos se basan en desencadenadores y se usa la variable de contexto Trigger.New.

Realización de operaciones en conjuntos de registros

En primer lugar, vamos a examinar el concepto de diseño masivo más básico de los desencadenadores. Los desencadenadores procesados de forma masiva realizan operaciones en todos los sObjects en el contexto de los desencadenadores. Normalmente, los desencadenadores realizan operaciones en un registro si la acción que ha activado el desencadenador se origina en la interfaz de usuario. Sin embargo, si el origen de la acción ha sido DML masivo o la API, el desencadenador realiza operaciones en un conjunto de registros en lugar de en un solo registro. Por ejemplo, si importa muchos registros con la API, los desencadenadores realizan operaciones en todo el conjunto de registros. Por consiguiente, una práctica de programación adecuada es dar por supuesto siempre que el desencadenador realiza operaciones en una colección de registros de modo que funcione en todos los casos.

En el caso del siguiente desencadenador, se da por supuesto que tan solo un registro ha activado el desencadenador. Este desencadenador no funciona en un conjunto de registros completo si se insertan varios registros en la misma transacción. En el siguiente ejemplo se muestra una versión procesada de forma masiva.

trigger MyTriggerNotBulk on Account(before insert) {
    Account a = Trigger.New[0];
    a.Description = 'New description';
}

Este ejemplo es una versión modificada de MyTrigger. Se usa un bucle for para iterar en todos los sObjects disponibles. Este bucle funciona si Trigger.New contiene un solo sObject o muchos sObjects.

trigger MyTriggerBulk on Account(before insert) {
    for(Account a : Trigger.New) {
        a.Description = 'New description';
    }
}

Aplicación de SOQL masivo

Las consultas SOQL pueden ser muy útiles. Puede recuperar registros relacionados y comprobar una combinación de múltiples condiciones en una sola consulta. Las funciones de SOQL permiten escribir menos código y realizar menos consultas a la base de datos. La reducción del número de consultas a la base de datos contribuye a evitar alcanzar los límites de consultas, los cuales se corresponden con 100 consultas SOQL para Apex sincrónico o 200 para Apex asíncrono.

En el siguiente desencadenador se muestra un patrón de consulta SOQL que se debe evitar. En el ejemplo se realiza una consulta SOQL en un bucle for para obtener las oportunidades relacionadas de cada cuenta, lo cual se ejecuta una vez para sObject de cuenta en Trigger.New. Si tiene una larga lista de cuentas, una consulta SOQL en un bucle for puede generar demasiadas consultas SOQL. En el siguiente ejemplo se muestra el método recomendado.

trigger SoqlTriggerNotBulk on Account(after update) {   
    for(Account a : Trigger.New) {
        // Get child records for each account
        // Inefficient SOQL query as it runs once for each account!
        Opportunity[] opps = [SELECT Id,Name,CloseDate 
                             FROM Opportunity WHERE AccountId=:a.Id];
        
        // Do some other processing
    }
}

Este ejemplo es una versión modificada de otra anterior y en él se muestra una mejor práctica para ejecutar consultas SOQL. La consulta SOQL hace la mayor parte del trabajo y se llama a ella una vez fuera del bucle principal.

  • La consulta SOQL usa una consulta interna (SELECT Id FROM Opportunities) para obtener las oportunidades relacionadas de las cuentas.
  • La consulta SOQL se conecta a los registros del contexto del desencadenador mediante el uso de la cláusula IN y el enlace de la variable Trigger.New en la cláusula WHERE (WHERE Id IN :Trigger.New). Esta condición WHERE filtra las cuentas para obtener solo los registros que han activado este desencadenador.

La combinación de las dos partes de los resultados de la consulta genera los registros correspondientes en una sola llamada (las cuentas de este desencadenador con las oportunidades relacionadas de cada cuenta).

Una vez obtenidos los registros y sus registros relacionados, el bucle for itera en los registros de interés mediante el uso de la variable de colección (en este caso, acctsWithOpps). La variable de colección contiene los resultados de la consulta SOQL. De este modo, el bucle for itera solo en los registros en los que se van a realizar operaciones. Dado que ya se han obtenido los registros relacionados, no se requieren consultas adicionales en el bucle para obtener dichos registros.

trigger SoqlTriggerBulk on Account(after update) {  
    // Perform SOQL query once.    
    // Get the accounts and their related opportunities.
    List<Account> acctsWithOpps = 
        [SELECT Id,(SELECT Id,Name,CloseDate FROM Opportunities) 
         FROM Account WHERE Id IN :Trigger.New];
  
    // Iterate over the returned accounts    
    for(Account a : acctsWithOpps) { 
        Opportunity[] relatedOpps = a.Opportunities;  
        // Do some other processing
    }
}

De forma alternativa, si no necesita los registros principales de cuenta, puede recuperar solo las oportunidades relacionadas con las cuentas en el contexto de este desencadenador. Esta lista se especifica en la cláusula WHERE mediante la coincidencia con el campo AccountId de la oportunidad con el Id. de las cuentas de Trigger.New: WHERE AccountId IN :Trigger.New. Las oportunidades devueltas son para todas las cuentas en el contexto de este desencadenador y no para una cuenta específica. En el siguiente ejemplo se muestra la consulta usada para obtener todas las oportunidades relacionadas.

trigger SoqlTriggerBulk on Account(after update) {  
    // Perform SOQL query once.    
    // Get the related opportunities for the accounts in this trigger.
    List<Opportunity> relatedOpps = [SELECT Id,Name,CloseDate FROM Opportunity
        WHERE AccountId IN :Trigger.New];
  
    // Iterate over the related opportunities    
    for(Opportunity opp : relatedOpps) { 
        // Do some other processing
    }
}

Puede reducir el tamaño del ejemplo anterior mediante la combinación de la consulta SOQL con el bucle for en una sola declaración (bucle for de SOQL). A continuación se muestra otra versión de este desencadenador masivo mediante un bucle for de SOQL.

trigger SoqlTriggerBulk on Account(after update) {  
    // Perform SOQL query once.    
    // Get the related opportunities for the accounts in this trigger,
    // and iterate over those records.
    for(Opportunity opp : [SELECT Id,Name,CloseDate FROM Opportunity
        WHERE AccountId IN :Trigger.New]) {
  
        // Do some other processing
    }
}

Más allá de lo básico

Los desencadenadores se ejecutan en lotes de 200 registros. Por lo tanto, si 400 registros activan un desencadenador, este se activa dos veces (una vez por cada 200 registros). Por este motivo, no puede aprovechar la ventaja del procesamiento de registros por lotes mediante el bucle for de SOQL en desencadenadores, ya que estos también procesan los registros por lotes. En este ejemplo se llama dos veces al bucle for de SOQL, pero también se llama dos veces a una consulta SOQL independiente. No obstante, el bucle for de SOQL sigue siendo más elegante que iterar en una variable de colección.

Aplicación de DML masivo

Al hacer llamadas a DML en un desencadenador o una clase, realice estas llamadas a DML en una colección de sObjects siempre que sea posible. La aplicación de DML a cada sObject individualmente implica un uso ineficiente de los recursos. El tiempo de ejecución de Apex permite hasta 150 llamadas a DML en una sola transacción.

Este desencadenador realiza una llamada de actualización en un bucle for que itera en las oportunidades relacionadas. Si se cumplen determinadas condiciones, el desencadenador actualiza la descripción de las oportunidades. En este ejemplo, se llama una vez a la declaración update de forma ineficiente para cada oportunidad. Si una operación de actualización de cuentas masiva activa el desencadenador, puede haber muchas cuentas. Si cada cuenta tiene una o dos oportunidades, podemos terminar fácilmente con más de 150 oportunidades. El límite de la declaración DML es de 150 llamadas.

trigger DmlTriggerNotBulk on Account(after update) {   
    // Get the related opportunities for the accounts in this trigger.        
    List<Opportunity> relatedOpps = [SELECT Id,Name,Probability FROM Opportunity
        WHERE AccountId IN :Trigger.New];          

    // Iterate over the related opportunities
    for(Opportunity opp : relatedOpps) {      
        // Update the description when probability is greater 
        // than 50% but less than 100% 
        if ((opp.Probability >= 50) && (opp.Probability < 100)) {
            opp.Description = 'New description for opportunity.';
            // Update once for each opportunity -- not efficient!
            update opp;
        }
    }    
}

Este siguiente ejemplo muestra cómo realizar declaraciones DML de forma masiva con una sola llamada a DML en una lista de oportunidades. En el ejemplo se agrega el sObject de oportunidad a una lista de oportunidades (oppsToUpdate) en el bucle. A continuación, el desencadenador realiza la llamada a DML fuera del bucle en la lista una vez que se han agregado todas las oportunidades a la lista. Este patrón usa solo una llamada a DML con independencia del número de sObjects que se van a actualizar.

trigger DmlTriggerBulk on Account(after update) {   
    // Get the related opportunities for the accounts in this trigger.        
    List<Opportunity> relatedOpps = [SELECT Id,Name,Probability FROM Opportunity
        WHERE AccountId IN :Trigger.New];
          
    List<Opportunity> oppsToUpdate = new List<Opportunity>();

    // Iterate over the related opportunities
    for(Opportunity opp : relatedOpps) {      
        // Update the description when probability is greater 
        // than 50% but less than 100% 
        if ((opp.Probability >= 50) && (opp.Probability < 100)) {
            opp.Description = 'New description for opportunity.';
            oppsToUpdate.add(opp);
        }
    }
    
    // Perform DML on a collection
    update oppsToUpdate;
}

Proceso de patrón de diseño masivo: ejemplo de desencadenador para la obtención de registros relacionados

Vamos a aplicar los patrones de diseño que ya conoce mediante la escritura de un desencadenador que acceda a las oportunidades relacionadas de las cuentas. Modifique el ejemplo de desencadenador de la unidad anterior para el desencadenador AddRelatedRecord. El desencadenador AddRelatedRecord realiza operaciones de forma masiva, pero no es tan eficiente como debería, ya que itera en todos los registros de sObject Trigger.New. En el siguiente ejemplo se modifica la consulta SOQL para obtener solo los registros de interés y, a continuación, iterar en dichos registros. Si no ha creado este desencadenador, no se preocupe, ya que puede hacerlo en esta sección.

Vamos a empezar por los requisitos para el desencadenador AddRelatedRecord. El desencadenador se activa una vez que se insertan o actualizan las cuentas. El desencadenador agrega una oportunidad predeterminada para cada cuenta que aún no tenga ninguna oportunidad. El primer problema es determinar cómo obtener registros de oportunidad secundarios. Dado que este es un desencadenador posterior, podemos consultar los registros afectados en la base de datos. Estos ya se han confirmado en el momento en el que se activa el desencadenador posterior. Vamos a escribir una consulta SOQL que devuelva todas las cuentas de este desencadenador que no tengan oportunidades relacionadas.

[SELECT Id,Name FROM Account WHERE Id IN :Trigger.New AND
                                             Id NOT IN (SELECT AccountId FROM Opportunity)]

Ahora que ya tenemos el subconjunto de registros de interés, vamos a iterar en estos registros mediante un bucle for de SOQL del modo siguiente.

for(Account a : [SELECT Id,Name FROM Account WHERE Id IN :Trigger.New AND
                                         Id NOT IN (SELECT AccountId FROM Opportunity)]){
}

Ya conoce los fundamentos de este desencadenador. La única pieza que falta es la creación de una oportunidad predeterminada, lo cual vamos a hacer de forma masiva. Este es el desencadenador completo.

  1. Si ya ha creado el desencadenador AddRelatedRecord en la unidad anterior, modifíquelo mediante la sustitución de su contenido por el siguiente desencadenador. En caso contrario, agregue el siguiente desencadenador mediante Developer Console e ingrese AddRelatedRecord como nombre del desencadenador.
    trigger AddRelatedRecord on Account(after insert, after update) {
        List<Opportunity> oppList = new List<Opportunity>();
        
        // Add an opportunity for each account if it doesn't already have one.
        // Iterate over accounts that are in this trigger but that don't have opportunities.
        for (Account a : [SELECT Id,Name FROM Account
                         WHERE Id IN :Trigger.New AND
                         Id NOT IN (SELECT AccountId FROM Opportunity)]) {
            // Add a default opportunity for this account
            oppList.add(new Opportunity(Name=a.Name + ' Opportunity',
                                       StageName='Prospecting',
                                       CloseDate=System.today().addMonths(1),
                                       AccountId=a.Id)); 
        }
        
        if (oppList.size() > 0) {
            insert oppList;
        }
    }
  2. Para probar el desencadenador, cree una cuenta en la interfaz de usuario de Salesforce y asígnele el nombre Lions & Cats.
  3. En la lista relacionada de oportunidades de la página de la cuenta, busque la oportunidad nueva Lions & Cats. El desencadenador ha agregado la oportunidad automáticamente.