3

I've always known that you can't make a callout after performing a DML operation because of holding the database transaction open, but I always assumed you could do a rollback prior. This is pseudo code for what I'm running via exec anonymous with SFDX:

SavePoint sp = Database.setsavepoint();
Product2 p = new Product2(External_Id__c = 'abc123');

Related_Object__c obj = new Related_Object__c();
obj.Product__r = p;

try {
    upsert obj;

} catch(System.DMLException e) {
    Database.rollback(sp);
    SomeClass.doCallout();
}

The upsert throws a DMLException because there's no record in Product2 with the external ID I've specified, but then in that exception handler I get the "Please commit or rollback before calling out" message on the SomeClass.doCallout() line, which as the name suggests, makes a call out. I feel like I'm missing something obvious here but I thought this was a valid use case for save points and rollbacks, not least given the wording of the message.

Is there no way to make a callout in this circumstance? I can re-work the code to query the database first of course, but wanted to avoid that if possible.

2 Answers 2

3

There are currently multiple checks occurring prior to the callout being allowed. The first, and most important, is that there isn't any uncommited work waiting on the transaction. That is what the that exception is all about.

We found internally that there is an additional check occurring that incorrectly flags the transaction as still having uncommited work, even though you have rolled back the SavePoint. For internal reference, that is W-8149756.

Thanks for raising this bug up, I'll increase the priority on getting this resolved.


Short repo:

Savepoint sp = Database.setSavepoint();
insert new Account(Name ='acme');
Database.rollback(sp);

HttpRequest req = new HttpRequest();
req.setMethod('GET');
req.setEndpoint('http://salesforce.com/');
(new Http()).send(req);

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

1
  • Appreciate the transparency on this one mate!
    – Matt Lacey
    Oct 3, 2022 at 9:25
3

Matt,

I think the crux of this isn't that the db transaction is 'open' it's that a db transaction has started at all. In the past, when I hit this, I've just wrapped my calling code in a Queueable or (shudder) @future. Or, better yet, wrap this entire code up as a monadic composition via Promises.

Promise promiseChain = new PromiseExtendingClass().then(new PromiseExtendingCalloutClass()).execute();

This is, of course a highly opinionated answer based on my own promise library/work.

1
  • Yeah ok - think I'm going to have to go for the "query first and see what's there" approach. Need this whole thing to be synchonous as it's the backend for a remoting call.
    – Matt Lacey
    Sep 30, 2022 at 8:40

You must log in to answer this question.

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