+ Start a Discussion
Mario VMario V 

Batch Apex, callouts and updates

I am stuck on this. I am getting the "You have uncommitted work pending. Please commit or rollback before calling out" CalloutException, even though I am doing the operations in the right order as I understand it.

The batch job has to select accounts that need updating, call a webservice to query some data, and update the accounts with this new information.

Below is a simplified version of my code. Basically:

- In the start() method I run a query on Account and return a QueryLocator
- In the execute() method I loop through the List<Account>,  do as many callouts as needed and set the relevant fields in the Account objects; at the end of the loop I do one update operation.

This should preserve the rule "no not do callouts after DML operations". But the second callout after I first set the fields is failing. It seems though setting field values on the object counts as DML, even though I am not performing the update operation yet.

Does that make sense? How can it be fixed? Or is it a whole different thing that I am doing wrong?

Thanks in advance.

 

global class AccountsUpdateWebService implements Database.Batchable<sObject>, Database.AllowsCallouts, Database.Stateful {
    global Database.QueryLocator start(Database.BatchableContext bc){
        String query = 'Select Id, Name FROM Account ...';
        return Database.getQueryLocator(query);
    }

    global void execute(Database.BatchableContext bc, list<Account> accounts) {
        List<Account> toupdate = new List<Account>();
        for (Account account: accounts) {
            // Callout to web service that retrieves address info
            WSData wsdata = WebService.getData (account.Name);
            account.BillingStreet = wsdata.Street;
            account.BillingPostalCode = wsdata.PostalCode;

            toupdate.add (accounts);
        }
        update toupdate;
    }

    global void finish(Database.BatchableContext bc) {
    }
}
Best Answer chosen by Mario V
Pankaj_GanwaniPankaj_Ganwani
Hi Mario,

Can't you set your scope size of batch to 1 or send multiple Accounts to webservice for processing in one go and updating the records based on the response received?

Thanks,
Pankaj

All Answers

Srinivas SSrinivas S
Hi Mario,

In 15th line please give toupdate.add (account);
You are adding all the accounts everytime. --->  toupdate.add (accounts);

------------
Thanks,
Srinivas
- Please mark as solution if your problem is resolved.
Pankaj_GanwaniPankaj_Ganwani
Hi mario,

Please use below mentioned code:
 
global class AccountsUpdateWebService implements Database.Batchable<sObject>, Database.AllowsCallouts, Database.Stateful {
    global Database.QueryLocator start(Database.BatchableContext bc){
        String query = 'Select Id, Name FROM Account ...';
        return Database.getQueryLocator(query);
    }

    global void execute(Database.BatchableContext bc, list<Account> accounts) {
        List<Account> toupdate = new List<Account>();
        for (Account account: accounts) {
            // Callout to web service that retrieves address info
            WSData wsdata = WebService.getData (account.Name);
            account.BillingStreet = wsdata.Street;
            account.BillingPostalCode = wsdata.PostalCode;

            toupdate.add (account);
        }
        update toupdate;
    }

    global void finish(Database.BatchableContext bc) {
    }
}

 
Mario VMario V

Thank you both for your answers. Actually the line toupdate.add (accounts); was a typo when simplifying my code for posting. I double checked my actual code and I am adding just one object at a time to the list.

I am getting the same result:

- Enter the for loop
- Call web service first time
- Set fields on account object
- Call web service second time -> CalloutException

Pankaj_GanwaniPankaj_Ganwani
Hi Mario,

Can't you set your scope size of batch to 1 or send multiple Accounts to webservice for processing in one go and updating the records based on the response received?

Thanks,
Pankaj
This was selected as the best answer
Mario VMario V
Changing the scope to 1 indeed solved it. Seems so obvious now. :) Thank you!!