Saltar al contenido

¿Cómo evitar un disparador recursivo que no sea el patrón clásico ‘clase con variable estática’?

Solución:

Otra buena forma de evitar la recursividad se destaca en el Capítulo 6 del excelente libro de Dan Applemans, Advanced Apex Programming. Este ejemplo en particular se refiere a la recursividad cuando se busca un cambio de campo específico que es muy común en muchos desencadenantes.

Como se menciona en la respuesta de @BarCotters, generalmente ejecuta su disparador con criterios muy específicos. Un cambio en un campo puede desencadenar cierta lógica en su disparador. Si observa el orden de operaciones para los desencadenantes, puede ver que las reglas de flujo de trabajo y las actualizaciones de campo basadas en esas reglas se ejecutan después de que se hayan ejecutado los desencadenadores antes y después. Sin embargo, se ejecuta antes de que los cambios se envíen realmente a la base de datos, que es donde esto puede causar problemas al detectar cambios de campo.

Entonces, digamos que estamos usando un ejemplo muy simple en el que tenemos un disparador en el objeto Oportunidad que busca oportunidades que se han ganado y, en base a eso, crea un nuevo registro de objeto personalizado.

trigger OpportunityTrigger on Opportunity (after update) {
    map<Id,Opportunity> justWonOppsMap = new map<Id,Opportunity>();
    for (Opportunity o : Trigger.new) {
        if (o.isWon != Trigger.oldMap.get(o.Id).isWon) {
            justWonOppsMap.put(o.Id, o);
        }
    }
    List<Some_Custom_Object__c> objs = new list<Some_Custom_Object__c>();
    for(Opportunity o : justWonOppsMap.values()){
         objs.add(new Some_Custom_Object__c(
              Name="New object based off " + o.Name, 
              Opportunity__c = o.Id
         ));
    }
    insert objs;
}

Este activador funcionará bien y creará un nuevo objeto personalizado cuando cierre una oportunidad. Es decir, funcionará bien, suponiendo que no tenga WFR que tengan actualizaciones de campo en el objeto de oportunidad.

Digamos que ahora tengo una regla de flujo de trabajo sobre la oportunidad que, cuando se cierra una Opp, cambia la fecha de cierre de la oportunidad a Hoy. (Un WFR bastante simple y común que muchos administradores pueden agregar).

Ese simple cambio ahora me rompe el gatillo. Este mismo disparador ahora realmente creará 2 objetos personalizados cuando mi oportunidad está cerrada. Esto se debe al hecho de que la regla WFR ahora activa los disparadores por última vez.

Así es como lo harías pensar la lógica funciona

  • Trigger Run 1 (valor anterior = no ganado, nuevo valor = ganado)
  • Activador de flujo de trabajo
  • Ejecutar 2 (valor anterior = ganado, nuevo valor = ganado)

Sin embargo, no es así como funciona, a pesar de que el opp se ha actualizado, aún no se ha comprometido con la base de datos, por lo que en la segunda ejecución del disparador todavía se ve el opp y simplemente se cierra como se muestra a continuación.

Así es como funciona realmente

  • Trigger Run 1 (valor anterior = no ganado, nuevo valor = ganado)
  • Flujo de trabajo
  • Trigger Run 2 (valor anterior = no ganado, nuevo valor = ganado) Idéntico a la primera ejecución

Aquí es donde entra en juego la solución de Dan Appleman. Él aconseja utilizar un mecanismo que realmente compruebe el “valor antiguo correcto”. Esto permitiría que la segunda ejecución del disparador detecte el valor que se estableció en la primera ejecución del disparador.

trigger OpportunityTrigger on Opportunity (after update) {
    OpportunityTriggerHelper.OppAfterUpdate(trigger.ew, trigger.old, trigger.newMap, trigger.oldMap);
}

public class OpportunityTriggerHelper {

    Private static Map <Id,boolean> oldIsWonMap = null;

    public static void OppAfterUpdate(list<Opportunity> newOpps, list<Opportunity> oldOpps, map<Id,Opportunity> newMap, map<Id,Opportunity> oldMap) {

        if(oldIsWonMap == null) {
            oldIsWonMap = new map<Id,boolean >();
        }

        map<Id,Opportunity> justWonOppsMap = new map<Id,Opportunity>();
        for (Opportunity o : Trigger.new) {

            //This checks to see if there was a value set in a previous trigger run
            boolean oldIsWon = (oldIsWonMap.containsKey(o.id)) ? oldIsWonMap.get(o.id) : oldmap.get(o.id).isWon;

            //this checks the current opp value with the 'correct' old value
            if(o.isWon && !oldIsWon){
                justWonOppsMap.put(o.Id, o);
            }

            //this puts in the 'correct' old value in case the trigger is run again in the same context
            if(oldIsWon != o.isWon) {
                oldIsWonMap.put(o.id,o.isWon);
            }
        }
        List<Some_Custom_Object__c> objs = new list<Some_Custom_Object__c>();
        for(Opportunity o : justWonOppsMap.values()){
            objs.add(new Some_Custom_Object__c(
                Name="New object based off " + o.Name, 
                Opportunity__c = o.Id
            ));
        }
        insert objs;
    }
}

Con este cambio, ahora la regla WFR no rompe nuestro gatillo. Solo se crea 1 objeto personalizado cuando se cierra el opp.

Sé que esto fue largo, pero espero que ayude. Me encantaría obtener este libro, ya que solo tiene unas pocas páginas de bondad, y este libro está repleto de un gran conocimiento que creo que todos los desarrolladores de SFDC deberían tener.

Aquí está el enlace de nuevo.

Advanced Apex Programming – 5th Edition

Su gatillo solo debe disparar según criterios muy específicos. Hágalos lo más específicos posible comparando valores de campo nuevos y antiguos para asegurarse de que se cumplan sus criterios.

Por ejemplo, si solo desea disparar cuando cambia la Fecha de nacimiento de un contacto, puede hacer lo siguiente:

trigger ContactBeforeTrigger on Contact (before insert, before update, before delete) {
    if (Trigger.isUpdate) {
        for (Contact contact : Trigger.new) {
            if (contact.Birthdate != Trigger.oldMap.get(contact.Id).Birthdate) {
                contact.SomeField__c="DOB Changed";
            }
        }
    }
}

Aunque el disparador está actualizando el contacto actual, no debemos preocuparnos por la recursividad porque:

  • El disparador solo se activará en las actualizaciones debido a la Trigger.isUpdate cheque
  • El disparador solo se activará cuando se cambie la fecha de nacimiento.

Si desea limitar cada disparador para disparar solo una vez, lo que describió sería la solución. SalesForce lo documenta aquí, pero puede ser un escenario válido que desencadena el disparo más de una vez en la misma transacción, el problema es si es un bucle infinito. Llegará a un límite de gobernador si hay más de 16 recurrencias del mismo activador.

Mantener una lista del último valor visto por el disparador como lo describe Chris Duncombe no es suficiente. Además del caso documentado de una actualización de campo de flujo de trabajo que provoca que un disparador se active por segunda vez con las mismas versiones antiguas / nuevas de un registro, hay un par de otros casos que puede que necesite manejar.

  • Actualizar eventos que se activan antes de insertar eventos
  • Ver la nueva versión de un registro en Trigger.old antes de ver la versión antigua

Ambos pueden ocurrir cuando hay varios desencadenantes en un objeto. (El segundo es relevante para la respuesta de Chris Duncombe). Considere los siguientes factores desencadenantes:

trigger MyTrigger on Account (after insert, after update) {
  for (Account a: Trigger.new) {
    if (Trigger.isInsert || a.My_Field__c != Trigger.oldMap.get(a.Id).My_Field__c) {
       ...
    }
  }
}

MyTrigger es el disparador que escribió, en el que está tratando de resolver el problema de doble disparo.

trigger TheirTrigger on Account (after insert, after update) {
  List<Account> updates = new List<Account>();
  for (Account a: Trigger.new) {
    // Assume there's a good reason for doing this in an after trigger
    updates.add(new Account(Id = a.Id, Their_Field__c = ...));
  }
  update updates;
}

TheirTrigger es otro disparador que actualiza el objeto utilizado por MyTrigger.

Cuando TheirTrigger incendios antes MyTrigger, pueden ocurrir los dos escenarios enumerados anteriormente. Después de un inserto, TheirTrigger actualiza el Account causando el MyTrigger disparar en un after update contexto. Si tienes diferente after insert y after update lógica, debes asegurarte de que MyTrigger todavía maneja el after insert evento cuando ocurre.

Ahora suponga My_Field__c se actualiza de “No” a “Sí”. Cuando TheirTrigger dispara primero, MyTrigger primero será llamado en un after update contexto en el que My_Field__c ya contiene “Sí” en Trigger.oldMap. Luego, después de la pila de llamadas para TheirTrigger se relaja, MyTrigger se dispara de nuevo con el valor anterior de “No” en Trigger.oldMap.

La mejor solución que he encontrado es mantener todas las versiones antiguas vistas por cada disparador. Cuando el disparador obtiene una versión de un objeto en Trigger.oldMap que ya se ha procesado debido a una actualización de campo de flujo de trabajo, puede ignorarlo; de lo contrario, debería comprobar los criterios específicos del disparador para decidir si lo procesa.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *