0

I'm trying to use Database.stateful interface in one of by batch to perform some action on list of AccountIDs and i want to keep track of the Accountids for whom action has already been performed in Execute method and in subsequent iteration want to skip those Accountids .

below is the code

public class Test_Batch implements Database.Batchable<sObject>, Database.AllowsCallouts,Database.Stateful{
    set<id> idsSet = new set<id>();
    public Database.QueryLocator start(Database.BatchableContext BC){

    string sQuery = 'Select id, Phone, Contact__c, Account__c from Locations__c';
    return Database.getQueryLocator(sQuery);
    }

    public void execute(Database.BatchableContext BC,   List<Locations__c> lstlocations ){
        if(lstlocations != null && lstlocations .size()>0){
            for(Locations__c oloc : lstlocations ){
                if(!idsSet.contains(oloc .Account__c)){
                    idsSet.add(oloc .Account__c);
                }
            }
        }
    }
    public void finish(Database.BatchableContext BC){
    }
}

But in every iteration the idsSet is getting reset, and if same AccountIds coming in different iteration of execute method then on same accountids the actions are being performed . I've tried using list & map the result is same . It's working fine and state being preserved if i use that set in Finish method but in execute method it's getting reset .

1
  • Please avoid adding tags which have nothing to do with what you are asking.
    – Adrian Larson
    Commented Jun 20, 2021 at 15:39

1 Answer 1

0

But in every iteration the idsSet is getting reset, and if same AccountIds coming in different iteration of execute method then on same accountids the actions are being performed.

No, idsSet is not being reset. Database.Stateful does work as advertised. The problem with your code is whatever you've omitted from this question. The most likely problem is that you have an uncaught exception, which prevents Database.Stateful from serializing the results of the transaction. That, or you have some other problem in your code.


Don't use a query "string" whenever possible, but instead use an inline query.

return Database.getQueryLocator([
    Select id, Phone, Contact__c, Account__c from Locations__c
]);

    if(lstlocations != null && lstlocations .size()>0){

The scope parameter of execute will never be null, and will never be empty. You do not need to check these conditions. More generally speaking, iterating over an empty list is always acceptable, so no need to check that as an entry condition.


if(!idsSet.contains(oloc .Account__c)){
    idsSet.add(oloc .Account__c);
}

First, you don't need to check if the value is already in the set, except to say that you want to do something for accounts not yet processed, so the more efficient way to write this is:

if(!idsSet.add(oloc.Account__c)) {
  continue; // We already have this account Id
}

The add method returns true if the set was modified by adding the value, false otherwise.


If you want to perform actions for the accounts only once, why not query for the accounts instead?

public Database.QueryLocator start(Database.BatchableContext context) {
    return Database.getQueryLocator([
        SELECT Id FROM Account WHERE Id In (SELECT Account__d FROM Locations__c)
    ]);
}
public void execute(Database.BatchableContext context, Account[] scope) {
    Locations__c[] locations = [SELECT Account__c, Phone__c, Contact__c FROM Locations__c WHERE Account__c = :scope];
    // rest of logic //
}
9
  • 1
    Alternatively the OP could be erroneously thinking that the idsSet would be shared across all separate instances of the batch (not the start/execute*n/finish flow of a single batch)?
    – Phil W
    Commented Jun 20, 2021 at 16:27
  • @PhilW That's an interesting thought. I guess we'll have to wait and see what they say.
    – sfdcfox
    Commented Jun 20, 2021 at 16:29
  • Thanks @sfdcfox , This was a really good explanation . The logic i'm performing in the execute method is that . I'm querying for all location and 100s of location can have 1 account , so suppose out of these 100s location record , 50 comes in one iteration of execute batch , then we do some logic then sends an email to the contact of that account . but in next iteration if same account comes in then another mail was triggring to that accounts Contact, i tried to use database.stateful to track the state of accountids , but with no success . but it's working completely fine in Finish Method. Commented Jun 20, 2021 at 17:14
  • @Salesforcegeek Yes, it sounds like you want to query by account in the start method, then send the relevant information. You don't need to track the accounts that way.
    – sfdcfox
    Commented Jun 20, 2021 at 17:16
  • @sfdcfox yup that would serve my purpose as well . But was generally confused with Why can't database.stateful preserve it's state in Execute method iterations but works like a charm in finish method . With the same approach , may be best approaches as you mentioned is not followed but still that set should have the accountids of previous iterations of execute method right ? then should be able to mitigate same accountids in subsequent iteration of execute method , but it adds back same ids again to the set and performs the email logic again for those account . Commented Jun 20, 2021 at 17:23

You must log in to answer this question.

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