Apex-Massenauslöser
Lernziele
Designmuster für Massenauslöser
Die Vorteile der Strukturierung von Code für Massenvorgänge ist, dass ein solcher Code eine große Anzahl von Datensätzen effizient verarbeiten und innerhalb der Obergrenzen auf der Lightning Platform ausgeführt werden kann. Diese Obergrenzen wurden etabliert, um sicherzustellen, dass unkontrollierter Code gemeinsam genutzte Ressourcen auf der mandantenfähigen Plattform nicht in Beschlag nimmt.
In den folgenden Abschnitten werden die wichtigsten Wege zur Strukturierung von Apex-Code in Auslösern für Massenvorgänge veranschaulicht: Verarbeiten aller Datensätze im Auslöser und Ausführen von SOQL und DML in Sammlungen von sObjects statt jeweils einzelnen sObjects. Die bewährten Vorgehensweisen für SOQL- und DML-Massenvorgänge gelten für jeden Apex-Code, einschließlich SOQL und DML in Klassen. Die aufgeführten Beispiele basieren auf Auslösern und verwenden die Kontextvariable Trigger.new
.
Verarbeiten von Datensatzgruppen
Sehen wir uns zuerst das einfachste Massen-Designkonzept in Auslösern an. Massenauslöser verarbeiten alle sObjects im Auslöserkontext. Die Auslöser verarbeiten in der Regel einen Datensatz, wenn die Aktion, durch die der Auslöser ausgeführt wurde, über die Benutzeroberfläche durchgeführt wurde. Wenn der Ursprung der Aktion jedoch ein DML-Massenvorgang oder die API war, verarbeitet der Auslöser eine Datensatzgruppe anstelle von nur einem Datensatz. Wenn Sie beispielsweise eine große Anzahl von Datensätzen über die API importieren, verarbeiten die Auslöser die gesamte Datensatzgruppe. Daher ist es gute Programmierpraxis, stets anzunehmen, dass der Auslöser eine Sammlung von Datensätzen verarbeitet, sodass er in allen Fällen funktioniert.
Im Fall des folgenden Auslösers (MyTriggerNotBulk
) wird angenommen, dass nur ein Datensatz zur Ausführung des Auslösers geführt hat. Dieser Auslöser verarbeitet nicht die gesamte Datensatzgruppe, wenn in derselben Transaktion mehrere Datensätze eingefügt werden. Eine Version für Massenvorgänge ist im nächsten Beispiel dargestellt.
trigger MyTriggerNotBulk on Account(before insert) { Account a = Trigger.new[0]; a.Description = 'New description'; }
Dieses Beispiel (MyTriggerBulk
) ist eine geänderte Version von MyTriggerNotBulk
. Darin wird eine for
-Schleife verwendet, um alle verfügbaren sObjects zu durchlaufen. Diese Schleife funktioniert, wenn Trigger.new
ein sObject oder mehrere sObjects enthält.
trigger MyTriggerBulk on Account(before insert) { for(Account a : Trigger.new) { a.Description = 'New description'; } }
Durchführen von SOQL-Massenvorgängen
SOQL-Abfragen können äußerst nützlich sein. Sie können verknüpfte Datensätze abrufen und eine Kombination von mehreren Bedingungen in einer Abfrage prüfen. Die Verwendung von SOQL-Funktionen ermöglicht es Ihnen, weniger Code zu schreiben und weniger Abfragen an die Datenbank zu senden. Weniger Datenbankabfragen bedeuten, dass Sie das Überschreiten zulässiger Obergrenzen für Abfragen vermeiden können. Diese liegen bei 100 SOQL-Abfragen für synchronen Apex bzw. 200 für asynchronen Apex.
Der folgende Auslöser (SoqlTriggerNotBulk
) zeigt ein SOQL-Abfragemuster, das vermieden werden sollte. In dem Beispiel wird eine SOQL-Abfrage innerhalb einer for
-Schleife ausgeführt, um die verknüpften Opportunities für jeden Account abzurufen. Diese wird für jedes Account-sObject in Trigger.new
je einmal ausgeführt. Wenn Ihre Liste der Accounts lang ist, kann eine SOQL-Abfrage innerhalb einer for
-Schleife zu viele SOQL-Abfragen ergeben. Im nächsten Beispiel wird die empfohlene Vorgehensweise gezeigt.
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 } }
Dieses Beispiel (SoqlTriggerBulk
) ist eine geänderte Version des vorherigen Beispiels und veranschaulicht eine bewährte Vorgehensweise zum Ausführen von SOQL-Abfragen. Die SOQL-Abfrage erledigt den Hauptteil und wird einmal außerhalb der Hauptschleife aufgerufen.
- Die SOQL-Abfrage verwendet eine innere Abfrage (
SELECT Id FROM Opportunities
), um verknüpfte Opportunities von Accounts abzurufen. - Die SOQL-Abfrage wird durch Verwendung der
IN
-Klausel und Bindung der VariablenTrigger.new
in der WHERE-Klausel (WHERE Id IN :Trigger.new
) mit den Datensätzen im Auslöserkontext verbunden. DieseWHERE
-Bedingung filtert die Accounts nach nur den Datensätzen, durch die dieser Auslöser ausgeführt wurde.
Durch Kombination der beiden Teile in der Abfrage werden die gewünschten Datensätze in einem Aufruf abgerufen, d. h. die Accounts in diesem Auslöser mit den verknüpften Opportunities jedes Accounts.
Nachdem die Datensätze und deren verknüpften Datensätze abgerufen wurden, durchläuft die for
-Schleife die relevanten Datensätze mithilfe Sammlungsvariablen, in diesem Fall acctsWithOpps
. Diese Sammlungsvariable enthält die Ergebnisse der SOQL-Abfrage. Auf diese Weise durchläuft die for
-Schleife nur die Datensätze, die wir verarbeiten möchten. Da die verknüpften Datensätze bereits abgerufen wurden, werden in der Schleife keine weiteren Abfragen zum Abrufen dieser Datensätze benötigt.
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 } }
Wenn Sie die übergeordneten Accountdatensätze nicht benötigen, können Sie alternativ nur die Opportunities abrufen, die mit den Accounts innerhalb dieses Auslöserkontexts verknüpft sind. Diese Liste wird in der WHERE
-Klausel durch Abgleich des Felds AccountId
der Opportunity mit der ID von Accounts in Trigger.new
angegeben: WHERE AccountId IN :Trigger.new
. Die zurückgegebenen Opportunities stammen von allen Accounts in diesem Auslöserkontext und nicht von einem spezifischen Account. Dieses nächste Beispiel zeigt die Abfrage, die zum Abrufen aller verknüpften Opportunities verwendet wird.
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 } }
Sie können das vorherige Beispiel verkürzen, indem Sie die SOQL-Abfrage mit der for
-Schleife in einer Anweisung kombinieren – und zwar zur SOQL-for
-Schleife. Nachstehend ist eine andere Version dieses Massenauslösers unter Verwendung einer SOQL-for
-Schleife dargestellt.
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 } }
Durchführen von DML-Massenvorgängen
Beim Durchführen von DML-Aufrufen in einem Auslöser oder in einer Klasse führen Sie nach Möglichkeit DML-Aufrufe bei einer Sammlung von sObjects durch. Das Durchführen von DML bei jedem einzelnen sObject bedeutet, dass Ressourcen ineffizient eingesetzt werden. Die Apex-Laufzeit lässt bis zu 150 DML-Aufrufe in einer Transaktion zu.
Dieser Auslöser (DmlTriggerNotBulk
) führt einen Aktualisierungsaufruf in einer for
-Schleife durch, die verknüpfte Opportunities durchläuft. Wenn bestimmte Bedingungen erfüllt sind, aktualisiert der Auslöser die Opportunity-Beschreibung. In diesem Beispiel wird die Aktualisierungsanweisung auf ineffiziente Weise je einmal für jede Opportunity aufgerufen. Wenn der Auslöser durch einen Massenvorgang zur Accountaktualisierung ausgeführt wurde, kann eine große Anzahl von Accounts vorliegen. Wenn jeder Account über ein oder zwei Opportunities verfügt, kann die Anzahl der Opportunities leicht über 150 betragen. Die Obergrenze für DML-Anweisungen liegt bei 150 Aufrufen.
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; } } }
Im nächsten Beispiel (DmlTriggerBulk
) wird gezeigt, wie Sie DML im Massenvorgang effizient mit nur einem DML-Aufruf in einer Liste der Opportunities ausführen. In dem Beispiel wird das Opportunity-sObject hinzugefügt, um eine Liste von Opportunities (oppsToUpdate
) in der Schleife zu aktualisieren. Nachdem alle Opportunities zur Liste hinzugefügt wurden, führt der Auslöser als Nächstes den DML-Aufruf außerhalb der Schleife bei dieser Liste durch. Dieses Muster verwendet nur einen DML-Aufruf, unabhängig von der Anzahl der aktualisierten sObjects.
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; }
Massen-Designmuster in Aktion: Beispiel zum Abrufen verknüpfter Datensätze
Wenden Sie die Designmuster, die Sie kennengelernt haben, an, indem Sie einen Auslöser schreiben, der auf verknüpfte Opportunities von Accounts zugreift. Ändern Sie dazu das Auslöserbeispiel aus dem vorherigen Abschnitt für den Auslöser "AddRelatedRecord
". Der Auslöser AddRelatedRecord
arbeitet mit einem Massenvorgang, ist jedoch nicht so effizient, wie er sein könnte, da er alle -sObject-Datensätze des Typs Trigger.new
durchläuft. Im nächsten Beispiel werden der Auslösercode und die SOQL-Abfrage so geändert, dass nur die relevanten Datensätze abgerufen und diese anschließend durchlaufen werden. Wenn Sie diesen Auslöser noch nicht erstellt haben, ist das kein Problem. Sie können ihn in diesem Abschnitt erstellen.
Beginnen wir mit den Anforderungen für den Auslöser AddRelatedRecord
. Der Auslöser wird ausgeführt, nachdem Accounts eingefügt oder aktualisiert wurden. Der Auslöser fügt für jeden Account, der nicht bereits über eine Opportunity verfügt, eine Standard-Opportunity hinzu.
Zunächst einmal haben neu eingefügte Accounts keine Standard-Opportunity, also müssen wir unbedingt eine hinzufügen. Doch bei aktualisierten Accounts müssen wir herausfinden, ob sie eine zugehörige Opportunity haben oder nicht. Lassen Sie uns also trennen, wie wir Einfügungen und Aktualisierungen verarbeiten, indem wir eine switch
-Anweisung für die Kontextvariable Trigger.operationType
angeben. Anschließend können Sie die zu verarbeitenden Accounts mit der Variablen toProcess
verfolgen. Beispiel:
List<Account> toProcess = null; switch on Trigger.operationType { when AFTER_INSERT { // do stuff } when AFTER_UPDATE { // do stuff } }
Bei allen Einfügungen von Accounts weisen wir die neuen Accounts einfach der Liste toProcess
zu:
when AFTER_INSERT { toProcess = Trigger.New; }
Bei Aktualisierungen müssen wir herausfinden, welche bestehenden Accounts in diesem Auslöser nicht bereits eine zugehörige Opportunity haben. Da es sich hier um einen "after"-Auslöser handelt, können die betreffenden Datensätze aus der Datenbank abgefragt werden. Hier ist die SOQL-Anweisung, deren Ergebnisse wir der Liste toProcess
zuweisen.
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)]; }
Wir arbeiten nun mit einer for
-Schleife, um die Liste toProcess
mit Accounts zu durchlaufen und eine zugehörige Standard-Opportunity zu oppList
hinzuzufügen. Wenn wir fertig sind, fügen wir die Liste der Opportunities mithilfe der DML-Anweisung insert
in einem Massenvorgang hinzu. Sie erfahren nun, wie Sie den vollständigen Auslöser erstellen oder aktualisieren.
- Wenn Sie den Auslöser
AddRelatedRecord
bereits im vorherigen Abschnitt erstellt haben, ändern Sie den Auslöser, indem Sie seinen Inhalt durch folgenden Auslöser ersetzen. Anderenfalls fügen Sie den folgenden Auslöser mithilfe der Entwicklerkonsole hinzu und geben Sie als AuslösernamenAddRelatedRecord
ein.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; } }
- Um den Auslöser zu testen, erstellen Sie auf der Salesforce-Benutzeroberfläche einen Account und nennen Sie ihn
Lions & Cats
. - Suchen Sie in der Themenliste "Opportunities" auf der Seite des Accounts nach der neuen Opportunity
Lions & Cats
. Der Auslöser hat die Opportunity automatisch hinzugefügt!