1

I'm getting

You have uncommitted work pending. Please commit or rollback before calling out

global class BatchTransactionSurvey implements Database.Batchable<ManualSurveyController.SurveyData>
                                             , DataBase.Stateful
                                             , Database.AllowsCallouts {

    private List<ManualSurveyController.SurveyData> data;
    private static List<String> results;

    global BatchTransactionSurvey(List<ManualSurveyController.SurveyData> data)
    {
        this.data = data;
        results = new List<String>();
    }

    global Iterable<ManualSurveyController.SurveyData> start(Database.BatchableContext BC)
    {
        return new TransactionSurveyIterable2(data);
    }

    global void execute(Database.BatchableContext BC, List<ManualSurveyController.SurveyData> scope)
    {
        for(ManualSurveyController.SurveyData d : scope)
        {
            String result;
            try{
                result = d.acctNum +': '+ TransactionSurveyREST.doPost(d.acctNum, d.activityID, d.processType, d.processSubType, d.rm, d.liaison, d.startDate, d.assetDescription, d.currentCustodian);
            }catch(Exception e)
            {
                result = d.acctNum +': '+ e.getMessage();
            }
            results.add(result);
        }
    }

    global void finish(Database.BatchableContext BC)
    {
        CustomEmail.sendBatchResultsEmail(BC.getJobId(), Constants.ADMIN_EMAIL);
        CustomEmail.sendSingleHtmlEmail(Constants.ADMIN_EMAIL, Constants.ADMIN_EMAIL, 'Transaction Survey Fix', 'Results', String.join(results, '\r\n') );
    }
}

My TransactionSurveyREST class takes the information does some processing and some SOQL statements if everything checks out it does a Callout to Clicktools which returns a URL as a string. The returned string is then used to create a new record

String surveyURL = ClickTools.getSurveyURL(surveyInstance, new List<String>{brandId, activityID, liaison, rm, processType});

Transaction_Survey__c survey = new Transaction_Survey__c(Survey_URL__c = surveyURL);

Database.saveResult sr = Database.insert(survey, false);

String result = '';
if(sr.isSuccess())
{
    result = Constants.SUCCESS;
} else
{
    for(database.error e: sr.getErrors())
    {
        result = result + e.getMessage() + Constants.NEW_LINE + Constants.NEW_LINE;
    }
}

I've initially set my Batch size to 5 to be well under the 10 Callouts per Batch Execute method. The odd thing is that the DML works for the first record in the Batch, but any subsequent records return the above error. So if I have 10 batches that each process 5 records, only 5 succeed and the rest fail with that error.

What can I do to make this work?

2

You need to save your DML statements until after you've done all your callouts. Ideally, this means tout need to bulkify your method so it can accept a list of wrapper classes.

Here's a roughly correct version of how the code might look like:

Transaction_Survey__c[] surveys = new Transaction_Survey__c[0];
String[] results = new String[0];
Integer[] indices = new Integer[0];

for(Integer index = 0, size = scope.size(); index < size; index++) {
    ManualSurveyController.SurveyData d = scope[index];

    try {
        // Adjust parameters as needed.
        surveys.add(new Transaction_Survey__c(Survey_URL__c = ClickTools.getSurveyURL(surveyInstance, new List<String>{d.brandId, d.activityID, d.liaison, d.rm, d.processType})); 
        indices.add(index);
        results.add(Constants.SUCCESS);
    } catch(CalloutException e) {
        results.add('ERROR: '+e.getMessage();
    }
}
Database.SaveResult[] saveResults = Database.insert(surveys, false);
for(Integer index = 0, size = saveResults.size(); index < size; index++) {
    if(!saveResults[index].isSuccess()) {
        results[indices[index]] = 'ERROR: '+saveResults[index].getErrors()[0].getMessage();
    }
}
return results;
  • My problem is that the TransactionSurveyREST class and it's doPost method is part of an integration with an outside system. That system calls the method singularly and cannot be changed. Normally this works just fine unfortunately it has failed on occasion and I created the batch class as a way to run records that fail. Is there any other way to work around this? – dBeltowski Nov 10 '15 at 16:26
  • Again, make a list of records from the result of each callout, then insert them all at once. Alternatively, set the batch size of your process to 1. – sfdcfox Nov 10 '15 at 16:38
  • @DougB I wrote an example of how your bulkified function might look like. It's kind of "ugly", but that's usually how it goes. We first do our callouts, then our DML operations, then render the results back to the caller. – sfdcfox Nov 10 '15 at 17:26
  • Talk about going above and beyond! Thank you for the help, I'll modify my code to follow your solution. – dBeltowski Nov 10 '15 at 18:48

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.