0

Thanks to another user (sfdcfox) I've got the following queueable apex being invoked via a screen flow that's being used to update account records. It's taking two string variables from the flow and then passing them into the queueable class to change the owner of Accounts. Being someone that's new to development I'm now starting to learn about Batch vs Queueable apex. Would I be better off using Batch apex for this instead? The goal is to mass update account owner based on the two parameters being passed in via the flow. I'm having trouble understanding if I'd want to use Batch vs Queueable apex for this (thousands of records are being updated). If Batch apex would in fact be the way to go, is there an easy way to convert this code? Not sure if it matters in my decision for using either one of the two but ultimately I'm going to wind up adding related objects to this so that when account owner changes, the opportunity and contact records are updated as well. Here's what I've got so far (that's working for accounts):

Class 1 that includes two string variables being set via the screen flow:

public class TerritoryAssignmentVariables {

@InvocableVariable
public String oldOwnerId;

@InvocableVariable
public String newOwnerId;

Class 2 for the Queueable apex that's accepting the variables from Class 1:

public class TerritoryAssignmentClass implements Queueable {

@InvocableMethod(label = 'Update Records')
public static void updateAccounts(List<TerritoryAssignmentVariables> IDValues){
    
List<Account> Updatedaccounts = new List<Account>();
    
    for(TerritoryAssignmentVariables value : IDValues){
        for(Account acc : [SELECT Id, Owner.Id FROM Account WHERE Owner.Id = :value.oldOwnerId]){
            acc.OwnerId = value.newOwnerId;
            Updatedaccounts.add(acc);
        }
    }
    System.enqueueJob(new TerritoryAssignmentClass(Updatedaccounts));
}
// State to update records //
Account[] scope;
TerritoryAssignmentClass(Account[] scope) {
    this.scope = scope;
}

// Queueable Context execution //
public void execute(QueueableContext ctx) {
    Account[] accountsToUpdate = new Account[0];
    
    // Adjust 100 as needed //
    while(accountsToUpdate.size()<100 && !scope.isEmpty()) {
        accountsToUpdate.add(scope.remove(0));
    }
    update accountsToUpdate;
    
    // We have more work to do, so enqueue again //
    if(!scope.isEmpty()) {
        System.enqueueJob(this);
    }
}

Thanks for any guidance you can provide.

0

In order to prevent a crash from killing your transaction entirely, I would suggest a Batchable class (or you could Queueable with Finalizer, but that may be overkill).

The transformation is actually pretty easy:

public class TerritoryAssignmentClass implements Database.Batchable<sObject> {
    @InvocableMethod(label = 'Update Records')
    public static void updateAccounts(List<TerritoryAssignmentVariables> IDValues){
        for(TerritoryAssignmentVariables item: IDValues) {
            Database.executeBatch(new TerritoryAssignmentClass(item));
        }
    }
    // State to update records //
    TerritoryAssignmentVariables item;
    TerritoryAssignmentClass(TerritoryAssignmentVariables item) {
        this.item = item;
    }
    public Database.QueryLocator start(Database.BatchableContext ctx) {
        return Database.getQueryLocator([SELECT Id FROM Account WHERE OwnerId = :item.oldOwnerId]);
    }

    // Queueable Context execution //
    public void execute(Database.BatchableContext ctx, Account[] scope) {
        for(Account record: scope) {
            record.OwnerId = item.newOwnerId;
        }
        update scope;
    }
}

However, as a queueable, you can also do this for up to 50 transfers at a time with:

public class TerritoryAssignmentClass implements Queueable {

@InvocableMethod(label = 'Update Records')
public static void updateAccounts(List<TerritoryAssignmentVariables> IDValues){
    for(TerritoryAssignmentVariables value : IDValues) {
        System.enqueueJob(new TerritoryAssignmentClass(value));
    }
}
// State to update records //
TerritoryAssignmentVariables item;
TerritoryAssignmentClass(TerritoryAssignmentVariables item) {
    this.item = item;
}

// Queueable Context execution //
public void execute(QueueableContext ctx) {
    Account[] accountsToUpdate = [SELECT Id FROM Account WHERE OwnerId = :item.oldOwnerId LIMIT 100];
    for(Account record: accountsToUpdate) {
        record.OwnerId = item.newOwnerId;
    }
    update accountsToUpdate;
    if([SELECT COUNT() FROM Account WHERE OwnerId = :item.oldOwnerId LIMIT 1] > 0) {
        System.enqueueJob(this);
    }
}

This is more scalable in the long run, but be sure to add error handling so that a crash won't result in missed transfers.

| improve this answer | |
  • Thanks sfdcfox, the batch is working great. I've created a second batch class to then update the Owner of Contact records. Do you know how I would pass the two User ID variables from the Account class into the Contact class (item.oldOwnerId and item.newOwnerId)? I've added the Contact class to this thread that I'm calling in the Finish() method of the first batch. I'm not sure how to use these values again in this second chain. – Matt Godfroy Jun 25 at 15:49
  • Update - got it! I called a method of class #2 within itself including the ID parameters that I'm passing to it from the Finish() method of batch class #1. – Matt Godfroy Jun 26 at 14:41
0

Here is the second class being called in the Finish() method of the first:

global class TerritoryAssignmentContacts implements Database.Batchable<sObject> {

global Database.QueryLocator start(Database.BatchableContext ctx) {
    String query = 'SELECT ID FROM Contact WHERE OwnerId = //not sure how to get ID from variable here//';
    return Database.getQueryLocator(query);
}

// Queueable Context execution //
global void execute(Database.BatchableContext ctx, List<Contact> scope) {
    for(Contact con: scope) {
        con.OwnerId = //not sure how to get ID from variable here//;
    }
    update scope;
}
global void finish (Database.BatchableContext BC){

}
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.