One of the requirements for out project is to do some complex logic on both setup and non-setup objects.
In short, whenever a field on Account is edited, we should recalculate our custom sharing for accounts, opportunities, leads and some custom objects. After that is done, all users associated with this account should be placed in a specified public group.
What we did first was a trigger, which failed the test with high volumes(Too many DML rows) because there are tens of thousands records that need re-sharing for a single account.
The second attempt is to use batch, but here's what happened:
- Re-sharing goes well(AccountShare, LeadShare, OpportunityShare, CustomObject__share records are deleted/inserted properly)
- Moving users between public groups doesn't work:
- By trying to call the method that did it(from the batch itself), we got First error: Future method cannot be called from a future or batch method
- Then we changed this method so that it's not
@future
anymore, but this raised the following error: First exception on row 0; first error: MIXED_DML_OPERATION, DML operation on setup object is not permitted after you have updated a non-setup object (or vice versa): GroupMember, original object: AccountShare - Finally, we modified the
finish()
method of the batch so that it updates these account records and we modified we trigger that it can catch this update and put the users in the correct public group, but the error we got was: First error: Too many future calls: 1
Between each of the steps we were "rolling back" the code.
So the question is:
How can we manipulate both setup and non-setup objects in a batch?
The class looks like this(this is not the actual code, it's heavily stripped for readability purposes):
global class BATCH_Dealer_Changed_Share implements Database.Batchable<sObject> {
Map<Id, Account> dealerAccs;
global BATCH_Dealer_Changed_Share() {
//
dealerAccs = new Map<Id, Account>([SELECT Id //and other fields
FROM Account
WHERE RecordTypeId = :UTIL_RecordTypes.AccountDealerRecordTypeId
AND New_Share__c != null]);
}
global Iterable<SObject> start(Database.BatchableContext BC) {
Iterable<SObject> myIter = (Iterable<SObject>) new ITER_Changing_Consolidation(dealerAccs.values());
// This iterable just returns me all the Share records(AccountShare, LeadShare, etc.) in one list
return myIter;
}
global void execute(Database.BatchableContext BC, List<SObject> scope) {
// a few hundred lines of complex logic for all share records
// Attempts 1 and 2 were here
try {
insert newShares;
delete oldShares;
// And also tried calling the method to move users here(attempts 1 and 2)
} catch(Exception e) {
// logging exception here if needed
}
}
global void finish(Database.BatchableContext BC) {
for (Account a : dealerAccs.values()) {
a.Share__c = a.New_Share__c;
a.New_Share__c = null;
}
update dealerAccs.values();
}
// Also tried to call that method here, but had 'Too many future calls: 1' error
}