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&lt;String, String&gt; contactRecord : contactRecords) {
    String contactId = contactRecord.get('Id ');
    if(campaignId != null &amp;&amp; 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!

robruf
  • 362
  • 1
  • 6
  • 21

2 Answers2

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

}

Adrian Larson
  • 149,971
  • 38
  • 239
  • 420
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&lt;String, String&gt; contactRecord : contactRecords) {
        String contactId = contactRecord.get('Id ');
        if(campaignId != null &amp;&amp; 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.

sfdcfox
  • 489,769
  • 21
  • 458
  • 806
  • 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 Aug 10 '21 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 Aug 10 '21 at 17:26