Skip to main content

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.
Nota

Nota

¿Su idioma de aprendizaje es español? Comience el reto en un Trailhead Playground en español y utilice las traducciones proporcionadas entre paréntesis para navegar. Copie y pegue solo los valores en inglés, ya que las validaciones del reto se basan en los datos en inglés. Si no aprueba el reto en su organización en español, le recomendamos que (1) cambie la configuración regional a Estados Unidos, (2) cambie el idioma a inglés, siga las instrucciones descritas aquí y, a continuación, (3) vuelva a hacer clic en el botón Check Challenge (Comprobar el reto).

Consulte la insignia Trailhead en su idioma para obtener más información sobre cómo aprovechar la experiencia de Trailhead en otros idiomas.

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, se mejora el rendimiento 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 eficaz 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 inquilinos.

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 (MyTriggerNotBulk), 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 (MyTriggerBulk) es una versión modificada de MyTriggerNotBulk. 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 (SoqlTriggerNotBulk) 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 cada 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
    }
}

El siguiente ejemplo (SoqlTriggerBulk) 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 utiliza 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 entre el campo AccountId de la oportunidad con el ID (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
    }
}





Nota

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 (DmlTriggerNotBulk) 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 (DmlTriggerBulk) 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 vayan 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;
}

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 el de la unidad anterior no es tan eficiente como debería, ya que itera en todos los registros del sObject Trigger.New. En el siguiente ejemplo se modifican tanto el código del desencadenador como 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 revisando 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. 

Antes que nada, las cuentas que se acaban de insertar nunca tienen una oportunidad predeterminada, así que tenemos que agregar una. En el caso de las cuentas actualizadas, debemos determinar si ya tienen una oportunidad relacionada. Vamos a diferenciar cómo se procesan las inserciones y las actualizaciones mediante una declaración switch en la variable de contexto Trigger.operationType. A continuación, rastrearemos las cuentas que tenemos que procesar con una variable toProcess. Por ejemplo:

List<Account> toProcess = null;
switch on Trigger.operationType {
    when AFTER_INSERT {
        // do stuff
    }
    when AFTER_UPDATE {
        // do stuff
    }
}

Para todo tipo de inserciones de cuentas, solo tenemos que asignar las cuentas nuevas a la lista toProcess:

when AFTER_INSERT {
     toProcess = Trigger.New;
}

En el caso de las actualizaciones, debemos determinar cuáles son las cuentas existentes de este desencadenador que no tienen ninguna oportunidad relacionada. Dado que este es un desencadenador posterior, podemos consultar los registros afectados en la base de datos. Esta es la declaración SOQL, el resultado de la asignación a la lista toProcess.

when AFTER_UPDATE {
     toProcess = [SELECT Id,Name FROM Account
                  WHERE Id IN :Trigger.New AND
                  Id NOT IN (SELECT AccountId FROM Opportunity WHERE AccountId in :Trigger.New)];
}

Ahora usamos un bucle for para iterar en la lista de cuentas de toProcess y agregar una oportunidad relacionada predeterminada a oppList. Una vez finalizado el proceso, agregamos de forma masiva la lista de oportunidades mediante la declaración DML insert.  Así es como se crea o actualiza todo el desencadenador.

  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 y escriba 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.
        List<Account> toProcess = null;
        switch on Trigger.operationType {
            when AFTER_INSERT {
            // All inserted Accounts will need the Opportunity, so there is no need to perform the query
                toProcess = Trigger.New;
            }
            when AFTER_UPDATE {
                toProcess = [SELECT Id,Name FROM Account
                             WHERE Id IN :Trigger.New AND
                             Id NOT IN (SELECT AccountId FROM Opportunity WHERE AccountId in :Trigger.New)];
            }
        }
        for (Account a : toProcess) {
            // 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 Opportunities (Oportunidades) de la página de la cuenta, busque la oportunidad nueva Lions & Cats. El desencadenador ha agregado la oportunidad automáticamente.

Recursos

Comparta sus comentarios sobre Trailhead en la Ayuda de Salesforce.

Nos encantaría conocer su experiencia con Trailhead. Ahora puede acceder al nuevo formulario de comentarios cuando quiera desde el sitio de la Ayuda de Salesforce.

Más información Continuar para compartir comentarios