Apex-Massenauslöser

Lernziele

Nachdem Sie diese Lektion abgeschlossen haben, sind Sie in der Lage, die folgenden Aufgaben auszuführen:
  • Schreiben von Auslösern, die Sammlungen von sObjects bearbeiten
  • Schreiben von Auslösern, die effiziente SOQL- und DML-Vorgänge durchführen

Designmuster für Massenauslöser

Apex-Auslöser sind für die Ausführung in einem Massenvorgang optimiert. Es wird empfohlen, für die Verarbeitung von Datensätzen in Auslösern Massen-Designmuster zu verwenden. Bei Verwendung von Massen-Designmustern zeigen Ihre Auslöser eine bessere Leistung, benötigen weniger Serverressourcen und überschreiten seltener Plattformobergrenzen.

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 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';
}

Das folgende Beispiel ist eine geänderte Version von "MyTrigger". 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 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
    }
}

Das folgende Beispiel 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 Variablen "Trigger.New" in der WHERE-Klausel (WHERE Id IN :Trigger.New) mit den Datensätzen im Auslöserkontext verbunden. Diese WHERE-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 und ihre zugehörigen Datensätze abgerufen wurden, durchläuft die For-Schleife die relevanten Datensätze mithilfe der 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
    }
}

Über die Grundlagen hinaus

Auslöser werden für Batches von je 200 Datensätzen ausgeführt. Wenn also 400 Datensätze zur Ausführung eines Auslösers führen, wird der Auslöser zweimal ausgeführt, je einmal für alle 200 Datensätze. Aus diesem Grund können Sie nicht von der Batchverarbeitung von Datensätzen mit einer SOQL-For-Schleife in Auslösern profitieren, da Auslöser Datensätze ebenfalls in Batches verarbeiten. Die SOQL-For-Schleife wird in diesem Beispiel zweimal aufgerufen, wie es auch bei einer eigenständigen SOQL-Abfrage der Fall wäre. Die SOQL-For-Schleife ist dennoch eine elegantere Lösung als das Durchlaufen einer Sammlungsvariablen.

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 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 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: Auslöserbeispiel 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 in einem Massenvorgang, ist jedoch nicht so effizient, wie er sein könnte, da er alle "Trigger.New"-sObject-Datensätze durchläuft. Im nächsten Beispiel wird 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. Das erste Problem, das in Angriff genommen werden muss, besteht darin, einen Weg zum Abrufen der untergeordneten Opportunity-Datensätze zu finden. Da es sich hier um einen after-Auslöser handelt, können die betreffenden Datensätze aus der Datenbank abgefragt werden. Sie wurden bis zur Ausführung des after-Auslösers bereits übergeben. Schreiben wir eine SOQL-Abfrage, die alle Accounts in diesem Auslöser zurückgibt, die über keine verknüpften Opportunities verfügen.

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

Jetzt, da wir den Teil der Datensätze abgerufen haben, an dem wir interessiert sind, lassen Sie uns diese Datensätze wie folgt mithilfe einer SOQL-For-Schleife durchlaufen.

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

Sie haben jetzt die Grundlagen unseres Auslösers kennengelernt. Es fehlt lediglich noch die Erstellung der Standard-Opportunity, die wir als Massenvorgang durchführen werden. Der vollständige Auslöser sieht wie folgt aus.

  1. 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ösernamen AddRelatedRecord 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.
        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. Um den Auslöser zu testen, erstellen Sie auf der Salesforce-Benutzeroberfläche einen Account und nennen Sie ihn Lions & Cats.
  3. 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!
Lernen Sie weiter kostenlos!
Registrieren Sie sich für einen Account, um fortzufahren.
Was ist für Sie drin?
  • Holen Sie sich personalisierte Empfehlungen für Ihre Karriereplanung
  • Erproben Sie Ihre Fähigkeiten mithilfe praktischer Aufgaben und Quizze
  • Verfolgen Sie Ihre Fortschritte nach und teilen Sie sie mit Arbeitgebern
  • Nutzen Sie Mentoren und Karrierechancen