function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
RiothamusRiothamus 

Multiple web service calls in a transaction

Hi,

I have an Apex program that loops through database records, makes a web service call for each, and stores the result in the record. The web services uses a token which expires after 24 hours, so a record may make up to 3 web service calls -- 1 to fail, 1 to get the token, and 1 to succeed.

 

From my understanding SalesForce limits web service calls to 10 per database transaction so as a brute-force method, I am doing an update for each record like this:

 

// initialize wrapper, scope
AccountWrapper wrapper = new AccountWrapper();
List<Account> scope = [SELECT Id, Name, BillingStreet, BillingCity, BillingState, BillingPostalCode FROM Account LIMIT 10];

// get districts
for (Account account: scope) {
wrapper.getDistrict(account, this.api);
update scope; // update scope to stay at or under Transaction Web Services governor limit
}

 

The Http web service requests are in wrapper.getDistrict. When I run this, I get a CalloutException:

 

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

 

My guess is that the update scope is not committing the transaction. How do I do a commit, or is there a better approach?

Best Answer chosen by Admin (Salesforce Developers) 
colemabcolemab

My google geocoding engine for salesforce (link here)  is a good working example of how to call out while having DML.

All Answers

colemabcolemab

You can't force the commit.  The commit will automatically happen at the end of the apex code execution.

 

The issue is that you are using a DML command (in this cae update) in between service calls.

 

You need to issue your DML command (in this case update) out side of your loop.   Program this like a trigger - NO DML calls in side of loops!

colemabcolemab

Also, it is 10 http callouts per apex code execution.   So if you need a max of 3 call outs per record, then you could only process 3 records which would give you a max of 9 call outs per execution and stay below the 10 callouts.

RiothamusRiothamus
Thank you for your response. If I move the update outside the loop, I will hit the maximum of 10 web service calls within a transaction, so I somehow need to get around that. Does the commit happen when the scope variable goes 'out of scope', e.g. the function exits and the scope variable is destroyed?
colemabcolemab

If you don't move your update outside of the loop, then you will not be allowed to call out again and you will get the uncommitted work error.  You simply can't make a call out after a DML statement.

 

I think that the commit will happen once the apex code has stopped executing (i.e. a transaction) all together.   It has nothing to do with a scope (of a function or variable).   This is why you can't explicitly commit to the database.

RiothamusRiothamus
Maximum of 10 http callouts per apex code execution? I thought it was per transaction. There must be a way around this. If the web service would allow me to batch the calls (e.g. pass and get back multiple arguments), that would work. Or I could design my own web service on another platform, which would call the provided web service once per argument, and then pass back the results. I would think about using a Salesforce batch, but I understand there is a limit of one HTTP call per batch execution. Can one batch call another? That would be messy and I'd like to avoid it, if it's even possible. Could I make the updates using @future? Thanks, Art
colemabcolemab

My google geocoding engine for salesforce (link here)  is a good working example of how to call out while having DML.

This was selected as the best answer
RiothamusRiothamus

Thanks, I'm reading your article now. Nice blog, good content and style!

colemabcolemab

Thanks for the comments.   Hopefully you can take that example, remove the geocoding part, and get it working.  Then you can use some kind of filtering method (like I did with the date check) and schedule the batches to get the call outs going.

RiothamusRiothamus

Excellent example, and very much like what I need.

 

I think the difference is that I can't cache my result -- the client runs my program when congressional districts change, a one-time event which essentially invalidates anything in my 'cache'. But what I could do is run my updater to mark all my districts as N/A or in need of resetting, and let the scheduled job find them.

 

Does that sound right to you?

colemabcolemab

If you are storing this in the database, then you are caching it out :-)    You just aren't updating it every time something changes (like my geo-coder is.).  So in your case the last geocode date being compared to the last modified date won't work for you.

 

That being said, you could use NA in your SOQL to filter and prevent the batch from running on the same set every time.  That is assuming you re-set all of your records to NA before scheduling your class to run and your schedule future method (i.e. call out) sets the fields to something other than NA.

 

It sounds like you are on the right track to me.