0

I need to parse a webservice result first to take actions afterwards.

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

I'm getting now this error, and there are a bunch of similar questions asked here or over the internet that i have tried to implement, but most of them are about doing the DML first and then calling the webservice.

According to this article, you can't have a webservice call and a DML in the same scope, and the solution is to do the DML and then declare another method for the webservice call in a different class.

But to my demise, i need to update the object with a bunch of values (around 15) obtained from the webservice call. And i could always declare setters or variables for each value and pass them as parameters to the next function, but there's a lot of them, so i'm looking for a more elegant solution.

Using onComplete on the commandButton looks like an equally bulky solution, and it would mess with my view by adding controller logic to it. Besides, each response object created by the "Generate from WSDL" use nested classes, adding more complexity to it.

Here is my controller logic, "servicio" is the instance of the webservice class

public void Login() {
    string alias = Usuario.Alias.split('@').get(0);

    //Here is where i do the WS call and get the values
    SalesforceTPSoap.ResultadoLogIn r = servicio.Login(this.IdEscritorio, Ofi, alias, Usuario.TP_Password__c);

    //now with the values, i need to get an object (TPSesion__c) and populate it with values, if it doesn't exist, i need to create it. any DML on this scope will trigger the error
    if (r.Err.CodError == 0){
        TPSesion__c[] tpsesion = [select s.Id from TPSesion__c s where s.OwnerId = :UserInfo.getUserId() and s.Hora_Login__c = :Date.today() limit 1];

        if (tpsesion.size() == 0){
            TPSesion__c tps = new TPSesion__c();
            tps.Usuario__c = Userinfo.getUserId();
            //this is just one of the updates
            tps.Hora_Login__c = r.Hora;
            tps.IdEscritorio__c = this.IdEscritorio;  
            insert tps;
        }
        else{
            TPSesion__c tps = tpsesion[0];
            tps.Cantidad_Login_Diaria__c++;
            update tps;
        }
    }
}

Thanks in advance

  • Unless you are making another callout or the insert/update is triggering a callout this error should not occur. When reviewing the debug logs what class / line is it telling you the error is thrown on. It may not even be the class / line you think it is.... – Eric Sep 8 '15 at 1:40
  • You can't perform DML operations and then make a callout. This limitation exists because in this context your callout could hold up the commit to the database while it was taking place (or waiting to time-out) and that will degrade platform database performance. Does your servicio.Login() method perform DML before it makes a callout? – Mark Pond Sep 8 '15 at 1:45
  • @MarkPond not at all, that's the full extent of my Login method, callout first, dml afterwards – FuuRe Sep 8 '15 at 2:37
  • @Eric I will get the full extent of the dev console output to a pastebin and add it to the post. Edit: tomorrow, as i don't have access to salesforce atm – FuuRe Sep 8 '15 at 2:40
  • Any retry happens may be due to access_token expiry or something like that? – highfive Sep 8 '15 at 6:00
1

So i finally stumbled upon the problem and the solution with the help of the developer console.

This is my page object in my view:

<apex:page sidebar="false" controller="SalesforceTP" cache="false" action="{!anAction}"> 

action="{!anAction}"

This method does calls to my webservice to get some values to update the UI, and you maybe would think that they are on a different context, well, they are not, the dev console clarified that for me, calling the action method and returning the page is a single transaction.

So the chain of execution was like this:

callout -> login method
dml -> login method
callout -> page action, triggered the error

I will try to break the context by moving some methods to another class or calling them by jQuery but that was it, removing the action from the page fixed the error.

0

I am not sure it will work or not. I am wondering if that if we can use Database.savepoint and rollback.

public void Login() {
    string alias = Usuario.Alias.split('@').get(0);

    SalesforceTPSoap.ResultadoLogIn r;  

    //Here is where i do the WS call and get the values
    Savepoint sp = Database.setSavepoint();

    r = servicio.Login(this.IdEscritorio, Ofi, alias, Usuario.TP_Password__c);

    Database.rollback(sp);

    //now with the values, i need to get an object (TPSesion__c) and populate it with values, if it doesn't exist, i need to create it. any DML on this scope will trigger the error
    if (r.Err.CodError == 0){
        TPSesion__c[] tpsesion = [select s.Id from TPSesion__c s where s.OwnerId = :UserInfo.getUserId() and s.Hora_Login__c = :Date.today() limit 1];

        if (tpsesion.size() == 0){
            TPSesion__c tps = new TPSesion__c();
            tps.Usuario__c = Userinfo.getUserId();
            //this is just one of the updates
            tps.Hora_Login__c = r.Hora;
            tps.IdEscritorio__c = this.IdEscritorio;  
            insert tps;
        }
        else{
            TPSesion__c tps = tpsesion[0];
            tps.Cantidad_Login_Diaria__c++;
            update tps;
        }
    }
}

Otherwise you will have only option of breaking the context. You can use oncomplete and actionfunction to break context.

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.