0

I have a method in an Apex class which is used in a visualforce page. I have been asked to move part of the method's code in a batch, since it seems that it is exceeding limits and causing errors.

Here is the code:

@RemoteAction
public static Map<String, String> create(List <Map<String, String>> contactRecords, String campaignId, String caseType, String caseSubType) {
    Map<String, String> result = new Map<String, String>();   
    List<Case> cases = new List<Case>();
    List<CampaignMember> cmList = new List<CampaignMember>();
    
    for (Map<String, String> contactRecord : contactRecords) {
        String contactId = contactRecord.get('Id ');
        if(campaignId != null && contactId != null){
            CampaignMember currentCamp = new CampaignMember(ContactId=Id.valueOf(contactId), CampaignId=campaignId);
            Case currentCase = new Case(Status='In Progress', unig__Channel__c='TELEMARKETING', Origin='Web',unig__Campaign__c=campaignId, ContactId=Id.valueOf(contactId), UNITA_Case_Type__c =caseType, UNITA_Case_Subtype__c = caseSubType, UNITA_From_Einstein__c=true);
            cases.add(currentCase);
            cmList.add(currentCamp);
            //System.debug('### currentCamp ' + currentCamp);
        }
    }
    try{
        //System.debug('### CAMPAING MEMBER LIST ' + cmList.size());
        //System.debug('### CASES LIST ' + cases.size());
        insert cmList;
        insert cases;
    }catch(Exception e){
        System.debug('### ERROR ' + e);
    }        
    
    for (Case currentCase2 : cases) {
        String currentCaseContactId = currentCase2.contactId;
        String currentCaseId = currentCase2.Id;
        result.put(currentCaseContactId, currentCaseId);
    }
    return result;
}

My question is: how do I implement the start method in the batch? I don't need a query, since the contacts are passed to the function as a parameter? How can the batch work with no query in the start method.

I have no experience at all with batches, so I might be missing something. Any help is much appreciated!

2 Answers 2

1

If you want to batch over a specific set of records, you still need a query. But you can pass in the Id values to a constructor or similar and store them in memory for later use in a query filter.

public class MyBatch implements Database.Batchable<SObject>
{
    final Set<Id> recordIds;
    public MyBatch(Set<Id> recordIds)
    {
        this.recordIds = recordIds;
    }
    
    public Database.QueryLocator start(Database.BatchableContext context)
    {
        return [SELECT ... FROM Contact WHERE Id IN :recordIds];
    }
    // other batch methods
}
2
  • 2
    You can return any kind of iterable, such as a list of records or even a list of Id values. It need not be a query. See for example, my answer here.
    – sfdcfox
    Commented Aug 10, 2021 at 14:09
  • 2
    I'm aware. I just find it the simplest option.
    – Adrian Larson
    Commented Aug 10, 2021 at 14:22
1

Don't use Batchable if you just need more CPU time. Instead, use Queueable. The translation is pretty straight-forward:

public class AsyncProcessor implements Queueable {
    List <Map<String, String>> contactRecords;
    String campaignId;
    String caseType;
    String caseSubType;
    public AsyncProcessor(List <Map<String, String>> contactRecords, String campaignId, String caseType, String caseSubType) {
        this.contactRecords = contactRecords;
        this.campaignId = campaignId;
        this.contactRecords = contactRecords;
        this.caseType = caseType;
    }
    public void execute(QueueableContext context) {
        Map<String, String> result = new Map<String, String>();   
        List<Case> cases = new List<Case>();
        List<CampaignMember> cmList = new List<CampaignMember>();
        
        for (Map<String, String> contactRecord : contactRecords) {
            String contactId = contactRecord.get('Id ');
            if(campaignId != null && contactId != null){
                CampaignMember currentCamp = new CampaignMember(ContactId=Id.valueOf(contactId), CampaignId=campaignId);
                Case currentCase = new Case(Status='In Progress', unig__Channel__c='TELEMARKETING', Origin='Web',unig__Campaign__c=campaignId, ContactId=Id.valueOf(contactId), UNITA_Case_Type__c =caseType, UNITA_Case_Subtype__c = caseSubType, UNITA_From_Einstein__c=true);
                cases.add(currentCase);
                cmList.add(currentCamp);
                //System.debug('### currentCamp ' + currentCamp);
            }
        }
        try{
            //System.debug('### CAMPAING MEMBER LIST ' + cmList.size());
            //System.debug('### CASES LIST ' + cases.size());
            insert cmList;
            insert cases;
        }catch(Exception e){
            System.debug('### ERROR ' + e);
        }        
        // Can't return the values from this context, though...
        for (Case currentCase2 : cases) {
            String currentCaseContactId = currentCase2.contactId;
            String currentCaseId = currentCase2.Id;
            result.put(currentCaseContactId, currentCaseId);
        }    
    }
}

@RemoteAction
public static void create(List <Map<String, String>> contactRecords, String campaignId, String caseType, String caseSubType) {
    System.enqueueJob(new AsyncProcessor(contactRecords, campaignId, caseType, caseSubType));
}

However, the problem with this is that you won't be able to get the results back. You'll have to deliver them via a separate mechanism, as Queueable/Batchable/Schedulable/future methods all run asynchronously. They won't help you solve your current situation, I think.

You may need to refactor your code to work as you'd like and get a response back. Perhaps consider writing your code to upload batches from the client side. I once wrote some code like this (I don't have it, so I can't share, unfortunately) where the client would adjust the batch size dynamically.

2
  • Uhm, I see. What if I'd just do the try/catch part in a queueable or batchable? Could that be a valid solution? That way I should still be able to return the results
    – robruf
    Commented Aug 10, 2021 at 15:49
  • @robruf If you need the improved CPU limits, and need asynchronous code, you cannot return the results, because the job will not start before the current transaction has ended (see this Q&A). As I stated, you'll need to somehow keep your code within synchronous limits if you want to get the return values, or use Platform Events to notify the client asynchronously.
    – sfdcfox
    Commented Aug 10, 2021 at 17:26

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .