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
Ravi NagarRavi Nagar 

Querying multiple objects in Batch Apex

My requirement was to look for all the records deleted by a particular user spanning mulitple objects and delete those records from recycle bin. I tried to come up with this code and it worked, but when there is too much of data it hits Apex governor limit. I am new to coding and don't know how to circumvent that. I initially tried writing my query in start method but I was not able to query multiple objects there using for loop.

I am using custom setting (EmptyRecycleBinBatch) to hold object API names.. Any suggestion is appreciated!

global class EmptyRecycleBinBatch implements Database.Batchable<sObject>, Schedulable {

    global Database.QueryLocator start(Database.BatchableContext BC) {
        String Query='Select Id, ObjectAPIName__c From EmptyRecycleBinBatch__c';
         return database.getquerylocator(Query);
   }

    global void execute(Database.BatchableContext BC,List<EmptyRecycleBinBatch__c> sObjRecords) {
        System.debug('in execute method');
        User userid= [Select Id, Name from User where User.Name= ' test user' And isActive= true Limit 1];
        String Query;
        
        for(EmptyRecycleBinBatch__c sOb: sObjRecords){ 
        
        SYstem.debug('object name :'+sOb.ObjectAPIName__c); 
          Query = 'SELECT ID from '+sOb.ObjectAPIName__c+' where isDeleted = true AND CreatedById = \''+userid.Id+'\' all rows';
        system.debug('query '+Query );            
        List<Sobject> sObjtocreate = new List<Sobject>();    
        sObjtocreate = Database.query(Query);
            System.debug('list of items to delete'+sObjtocreate);
      if(sObjtocreate != null && !sObjtocreate.isEmpty()) { 
          
     try{
         //system.debug(' deleting :'+sObjtocreate);
           Database.emptyRecycleBin(sObjtocreate); 
            if(test.isRunningTest())
            {
                 throw new applicationException('Exception');
            }
        }
        catch(Exception e){
            System.debug('ERROR on Delete:' + e);
        }
       }
      }
     }     
    public class applicationException extends Exception 
    {
    
    } 
    global void finish(Database.BatchableContext BC) {
        
    }
    global void execute(SchedulableContext sc) {
        database.executebatch(new EmptyRecycleBinBatch());
    }
}
Shashikant SharmaShashikant Sharma
You could use custom iterable for this case.
 
global class batchClass implements Database.batchable<SObject>{ 
   
   global Iterable<Sobject> start(Database.batchableContext info){ 
       Iterable<SObject> myIter = (Iterable<SObject>) new CustomIterable();
       return myIter; 
   }     
   
   global void execute(Database.batchableContext info, List<SObject> scope){ 
       
   }     
   global void finish(Database.batchableContext info){     
   } 
}
 
global class CustomIterable implements Iterator<SObject>{ 
   
   List<SObject> sobjs {get; set;} 
   Integer i {get; set;} 
   public CustomIterable(){ 
       sobjs = new List<SObject>();
       
       List<Account> accs = [SELECT id, name, numberofEmployees FROM Account WHERE name !=: NULL ]; 
       for( Account acc : accs ) {
           sobjs.add(acc);
       }
       
       List<Contact> cons = [SELECT id, name FROM Contact WHERE lastname !=: Null ]; 
       for( Contact con : cons ) {
           sobjs.add(con);
       }
       i = 0; 
   }   
   global boolean hasNext(){ 
       if(i >= sobjs.size()) 
           return false; 
       else 
           return true; 
   }    
   global SObject next(){ 
       if(i == 8){ i++; return null;} 
       i=i+1; 
       return sobjs[i-1]; 
   } 
}

Thanks
Shashikant
Ravi NagarRavi Nagar
Thanks for your response. Even if it works, in this case I will have to code all the object names that I wanted to avoid. I was using custom setting to hold object  names so anyone can add/remove objects using salesforce UI.
Shashikant SharmaShashikant Sharma
You could easily use custom setting and dynamic queries to get objects one by one

your flow in that case will be 
  1. fetch ojects to query from custom settings
  2. loop over the API Names and run dynamic query one by one
  3. loop over records from query and add items to sobject list
Thanks
Shashikant
VamsiVamsi
HI Shashikant Sharma,

I have used your approach to full fill my req (Update case when an article is attached to it). But it seems to be trowing an error when casting the scope variable to casearticle (Since scope consists of both case records and casearticle records ). Could you please let me know how to differentiate between 2 object records before casting them to respective objects.

Batch Apex 
global class CArticlebatchClass implements    Database.batchable<SObject> { 
      global Iterable<Sobject> start(Database.batchableContext bc)   { 
      Iterable<SObject> myIter = (Iterable<SObject>) new CAArticleCustomIterable();
      return myIter;     }     
      global void execute(Database.batchableContext info, List<sobject>scope)    { 
     list<case> Finalupdatelist = new list<Case>();
     Map<ID,CaseArticle> Articlelist = new Map<ID,CaseArticle>();
     //list<Case> calist = (list<case>)scope;
      //system.debug('list of cases'+calist);
     for(SObject so : scope)
     { 
         system.debug('scope value'+ scope);
        CaseArticle can = (CaseArticle)so;  // Error at this line: Invalid 
                                            conversion from runtime type 
                                             Case to CaseArticle
         Articlelist.put(can.caseid,can);
     }

      for(SObject sc:Scope)
       {
           Case cas = (case)sc;
       if(Articlelist.containsKey(cas.Id))
       {
           cas.Article_Attached__c = True;
            Finalupdatelist.add(cas);
       }
       else
       {
            cas.Article_Attached__c = false;
            Finalupdatelist.add(cas);
        }
       }

       If(Finalupdatelist.size()>0)
       {
           update Finalupdatelist;
       }
    }
           global void finish(Database.batchableContext bc)    { 
              }  }

Iterbale 
 
global class CAArticleCustomIterable implements    Iterable<SObject>,Iterator<SObject>{ 
      public Iterator<SObject> iterator() { return this; }    List<SObject> sobjs {get; set;}     Integer i {get; set;}     Id    custid =    Schema.SObjectType.case.getRecordTypeInfosByName().get('Customer    Support').getRecordTypeId();     public CAArticleCustomIterable ()       { 
      sobjs = new List<SObject>();

      List<CaseArticle > Carticle= [select id,caseid from CaseArticle]; 
      for( CaseArticle ca: Carticle) {
          sobjs.add(ca);
      }

      List<Case> cons =  [select id,Article_Attached__c from case where RecordTypeID =:custid AND Status = 'Closed']; 
      for(Case con : cons ) {
          sobjs.add(con);
      }
      i = 0;     }       global boolean hasNext(){ 
      if(i >= sobjs.size()) 
          return false; 
      else 
          return true;     }        global SObject next(){ 
      if(i == 8){ i++; return null;} 
      i=i+1; 
      return sobjs[i-1];     }  }

 
Shweta Tiwari 7Shweta Tiwari 7
Hi Vamsi,
I am getting similar error. Were you able to fix the error ?

Thanks!!
VamsiVamsi
Hi Shweta , Looking for the same business requirement ..?
Shweta Tiwari 7Shweta Tiwari 7
yes Vamsi, I have similar requirement where I have to Query multiple objects in a single batch. Those objects are not related. I was cecking this example but getting same error. Were u able to resolve the issue ?
VamsiVamsi
Hi Swetha, Since iterable interface would return the combination of both the records for each iteration, the execute method will consists of both the records , So type casting it to a single object causes the error. //Every object record id will have common set of char's and in my case its case article and case object Map Casearticlemap = new Map(); Map Casemap = new Map(); string casearticleobj = '0gv'; string caseobj = '500'; for(sobject ad :scope) { string IDs = (String)ad.get('ID'); system.debug('IDs'+IDs); if(IDs.startswith(casearticleobj)) { Casearticlemap.put(ad.id,ad); } else if(IDs.startswith(caseobj)) { Casemap.put(ad.id,ad); } } once you are done with segregating the objects as above then make use of collections contains these object records to type case to respective objects as required . Hope this helps ...!!!
ankaiah bandi 6ankaiah bandi 6
Hi Vamsi,
i need to implement same requirement as like your requirement... if it is working, can you plase your code ?