Déclencheurs Apex en masse
Objectifs de formation
Modèles de conception de déclencheurs en masse
L’activation du traitement en masse dans votre code permet de traiter efficacement un grand nombre d’enregistrements et de respecter les limitations du gouverneur sur la plate-forme Lightning Platform. Ces limitations du gouverneur empêchent l'échappement du code pour éviter qu'il ne monopolise pas les ressources sur la plate-forme mutualisée.
Les sections suivantes présentent les principales méthodes de mise en masse de votre code Apex dans des déclencheurs : fonctionnement dans tous les enregistrements du déclencheur, et exécution de requêtes SOQL et DML sur des collections de sObjects au lieu d'un seul sObject à la fois. Les meilleures pratiques d'utilisation de requêtes SOQL et DML en masse s'appliquent à tous les codes Apex, y compris SOQL et DML dans des classes. Les exemples présentés s’appuient sur des déclencheurs et utilisent la variable de contexte Trigger.new
.
Fonctionnement dans des collections d'enregistrements
Examinons d'abord la conception en masse de base dans les déclencheurs. Les déclencheurs en masse doivent fonctionner sur tous les sObjects dans le contexte du déclencheur. Généralement, les déclencheurs fonctionnent sur un enregistrement si l'action qui les a déclenchés provient de l'interface utilisateur. Néanmoins, s'il provient d'une requête DML en masse ou de l'API, le déclencheur fonctionne sur plusieurs enregistrements plutôt que sur un enregistrement unique. Par exemple, lorsque vous importez de nombreux enregistrements via l'API, les déclencheurs fonctionnent sur l'ensemble complet d'enregistrements. Par conséquent, en programmation il est recommandé de toujours considérer que le déclencheur fonctionne dans une collection d'enregistrements pour s'assurer qu'il fonctionne dans toutes les situations.
L’exemple de déclencheur suivant (MyTriggerNotBulk
) considère qu’un seul enregistrement a entraîné l’activation du déclencheur. Ce déclencheur ne fonctionne pas sur un ensemble complet d'enregistrements lorsque plusieurs enregistrements sont insérés dans la même transaction. Une version en masse est illustrée dans l'exemple suivant.
trigger MyTriggerNotBulk on Account(before insert) { Account a = Trigger.new[0]; a.Description = 'New description'; }
Cet exemple (MyTriggerNotBulk
) est une version modifiée de MyTriggerNotBulk
. Il utilise une boucle for
pour itérer sur tous les sObjects disponibles. Cette boucle fonctionne si Trigger.new
contient un sObject ou plusieurs sObjects.
trigger MyTriggerBulk on Account(before insert) { for(Account a : Trigger.new) { a.Description = 'New description'; } }
Exécution d'appels SOQL en masse
Les requêtes SOQL peuvent être puissantes. Vous pouvez récupérer des enregistrements associés et vérifier une combinaison de plusieurs conditions dans une seule requête. En utilisant les fonctionnalités SOQL, vous pouvez réduire l'écriture de code et diminuer le nombre de requêtes à la base de données. La diminution des requêtes à la base de données permet de ne pas atteindre les limitations en requêtes, qui sont de 100 requêtes SOQL pour un Apex synchrone, ou 200 pour un Apex asynchrone.
Le déclencheur suivant (SoqlTriggerNotBulk
) montre un modèle de requête SOQL à éviter. L’exemple émet une requête SOQL dans une boucle for
afin de récupérer les opportunités associées de chaque compte, qui est exécuté une fois pour chaque sObject Compte dans Trigger.new
. Si votre liste de comptes est longue, une requête SOQL dans une boucle for
peut générer un nombre de requêtes SOQL excessif. L'exemple suivant montre l'approche recommandée.
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 } }
L’exemple suivant (SoqlTriggerBulk
) est une version modifiée du précédent et présente une bonne pratique d’exécution de requêtes SOQL. La requête SOQL effectue la plus grande partie du travail. Elle est appelée une fois hors de la boucle principale.
- La requête SOQL utilise une requête interne, (
SELECT Id FROM Opportunities
), pour obtenir les opportunités associées des comptes. - La requête SOQL est connectée aux enregistrements de contexte du déclencheur en utilisant la clause
IN
et en liant la variableTrigger.new
dans la clauseWHERE
:WHERE Id IN :Trigger.new
. Cette conditionWHERE
filtre les comptes pour retenir uniquement ceux qui ont activé ce déclencheur.
La combinaison des deux parties dans la requête renvoie les enregistrements voulus dans un seul appel : les comptes de ce déclencheur avec les opportunités associées à chaque compte.
Une fois les enregistrements et leurs enregistrements associés récupérés, la boucle for
itère sur les enregistrements intéressants à l’aide de la variable de collection, dans le cas présent, acctsWithOpps
. La variable de collection contient les résultats de la requête SOQL. Ainsi, la boucle for
itère uniquement sur les enregistrements souhaités. Les enregistrements associés sont étant déjà récupérés, aucune autre requête n'est nécessaire dans la boucle.
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 } }
Si les enregistrements parents du compte ne sont pas nécessaires, vous pouvez aussi récupérer uniquement les opportunités associées aux comptes dans le contexte de ce déclencheur. Cette liste est spécifiée dans la clause WHERE
en mappant le champ AccountId
de l’opportunité avec l’ID
des comptes dans Trigger.new
: WHERE AccountId IN :Trigger.new
. Les opportunités sont renvoyées pour tous les comptes dans le contexte de ce déclencheur, pas pour un compte spécifique. L'exemple suivant présente la requête utilisée pour récupérer toutes les opportunités associées.
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 } }
Vous pouvez réduire la taille de l’exemple précédent en combinant la requête SOQL avec la boucle for
dans une instruction : la boucle for
SOQL. Voici une autre version de ce déclencheur en masse utilisant une boucle for
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 } }
Exécution d'appels DML en masse
Lors de l'exécution d'appels DML dans un déclencheur ou une classe, essayez de les exécuter sur une collection de sObjects. Lors de l'exécution de DML sur chaque sObject individuel, les ressources ne sont pas exécutées efficacement. L'exécution d'Apex permet jusqu'à 150 appels DML dans une transaction.
Le déclencheur (DmlTriggerNotBulk
) effectue un appel de mise à jour dans une boucle for
qui tourne sur les opportunités associées. Si certaines conditions sont remplies, le déclencheur met à jour la description de l'opportunité. Dans cet exemple, l'instruction de mise à jour est appelée une fois pour chaque opportunité, ce qui est inefficace. Si une opération d'une mise à jour de comptes en masse a activé le déclencheur, le nombre de comptes peut être important. Si chaque compte a une ou deux opportunités, vous risquez de vous retrouver rapidement avec plus de 150 opportunités. La limitation en instructions DML est de 150 appels.
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; } } }
L’exemple suivant (DmlTriggerBulk
) montre comment exécuter efficacement le langage DML en masse avec un seul appel DML sur une liste d’opportunités. Il ajoute le sObject Opportunité pour mettre à jour une liste d'opportunités (oppsToUpdate
) dans la boucle. Le déclencheur exécute ensuite l'appel DML hors de la boucle sur cette liste, lorsque toutes les opportunités sont ajoutées à la liste. Ce modèle utilise un seul appel DML, quel que soit le nombre de sObjects mis à jour.
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; }
Modèle de conception en masse dans une action : Exemple de récupération d’enregistrements associés
Appliquons les modèles de conception que nous venons de voir en écrivant un déclencheur qui accède aux opportunités associées des comptes. Remplacez l'exemple de déclencheur de l'unité précédente par le déclencheur AddRelatedRecord
. Le déclencheur AddRelatedRecord
fonctionne en masse, mais celui que nous avons abordé dans l’unité précédente n’est pas aussi efficace qu’il pourrait l’être, car il itère sur tous les enregistrements de sObject Trigger.New
. L’exemple suivant modifie à la fois le code de déclencheur et la requête SOQL pour récupérer uniquement les enregistrements intéressants, avant d’itérer sur ces enregistrements. Si vous n'avez pas créé ce déclencheur, ne vous inquiétez pas, vous le ferez dans cette section.
Commençons par étudier les conditions requises pour le déclencheur AddRelatedRecord
. Le déclencheur est activé lorsque des comptes sont insérés ou mis à jour. Le déclencheur ajoute une opportunité par défaut pour chaque compte qui n'a pas encore d'opportunité.
Tout d’abord, les nouveaux comptes insérés n’ont jamais d’opportunité par défaut, nous devons donc commencer par en ajouter une. En ce qui concerne les comptes mis à jour, nous devons déterminer s’ils ont une opportunité associée. Faisons donc une distinction entre le traitement des insertions et celui des mises à jour en utilisant une instruction switch
dans la variable de contexte Trigger.operationType
. Identifions ensuite les comptes que nous devons traiter avec une variable toProcess
. Par exemple :
List<Account> toProcess = null; switch on Trigger.operationType { when AFTER_INSERT { // do stuff } when AFTER_UPDATE { // do stuff } }
Pour toutes les insertions de compte, nous attribuons simplement les nouveaux comptes à la liste toProcess
:
when AFTER_INSERT { toProcess = Trigger.New; }
Pour les mises à jour, nous devons déterminer quels comptes existants dans ce déclencheur n’ont pas encore d’opportunité associée. Il s’agit d’un déclencheur after. Par conséquent, nous pouvons interroger les enregistrements affectés dans la base de données. Voici l’instruction SOQL, dont nous attribuons les résultats à la liste 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)]; }
Nous utilisons maintenant une boucle for
pour parcourir la liste de comptes toProcess
et ajoutons une opportunité par défaut associée à oppList
. Lorsque nous avons terminé, nous ajoutons en masse la liste des opportunités à l’aide de l’instruction DML insert
. Voici comment créer ou mettre à jour le déclencheur complet.
- Si vous avez déjà créé le déclencheur
AddRelatedRecord
dans l'unité précédente, modifiez-le en remplaçant son contenu par le déclencheur ci-dessous. Sinon, ajoutez le déclencheur suivant en utilisant la Developer Console et nommez-leAddRelatedRecord
.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; } }
- Pour tester le déclencheur, créez un compte dans l'interface utilisateur de Salesforce et nommez-le
Lions & Cats
(Lions et chats). - Dans la liste associée Opportunities (Opportunités) de la page du compte, recherchez la nouvelle opportunité
Lions & Cats
(Lions et chats). Le déclencheur a automatiquement ajouté l'opportunité !