What’s the Salesforce limitation that annoys you the most?
Coming from a background of self-hosted relational databases (pleading guilty to PHP), it has definitely been the ridiculously low amount of records that one can query (50,000) and update (10,000) in one go.
Sure, there’s multitenancy and stuff. But there’s also your need of processing more records in a transaction than Salesforce limits allow.
That’s where the RecordBatchInserter, RecordBatchUpserter and RecordBatchDeleter classes come in handy.
These batch classes are fed with a list of instantiated, but not yet manipulated records (for insert and upsert), or a simple SOQL query string (for delete) respectively.
To call the classes, it’s sufficient to keep an if clause in your productive code, which manipulates the records in a traditional way, as long as the record count does not get close to the limit – and otherwise calls the batches.
if(recordsToInsert.size() > 10000){
Database.executeBatch(new RecordBatchInserter(recordsToInsert));
} else {
Database.insert(recordsToInsert, false);
}
RecordBatchInserter
global class RecordBatchInserter implements Database.Stateful, Database.batchable<sObject>{
global List<sObject> myList;
global RecordBatchInserter(List<sObject> objList){
myList = objList;
}
global List<sObject> start(Database.BatchableContext bc) {
return myList;
}
global void execute(Database.BatchableContext bc, List<sObject> scope){
Database.insert(scope, false);
}
global void finish(Database.BatchableContext bc){
// do nothing or notify the technical team
}
}
RecordBatchUpserter
global class RecordBatchUpserter implements Database.Stateful, Database.batchable<sObject>{
global List<sObject> myList;
global Schema.sObjectField upsertField;
global RecordBatchUpserter(List<sObject> objList){
this.myList = objList;
this.upsertField = null;
}
global RecordBatchUpserter(List<sObject> objList, Schema.sObjectField upsertField){
this.myList = objList;
this.upsertField = upsertField;
}
global List<sObject> start(Database.BatchableContext bc) {
return this.myList;
}
global void execute(Database.BatchableContext bc, List<sObject> scope){
if(this.upsertField == null){
Database.upsert(scope, false);
} else {
Database.upsert(scope, upsertField, false);
}
}
global void finish(Database.BatchableContext bc){
// do nothing or notify the technical team
}
}
RecordBatchDeleter
global class RecordBatchDeleter implements Database.batchable<sObject>{
global String queryString;
global RecordBatchDeleter(String qString){
queryString = qString;
}
global Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator(this.queryString);
}
global void execute(Database.BatchableContext bc, List<sObject> scope){
Database.delete(scope, false);
}
global void finish(Database.BatchableContext bc){
// do nothing or notify the technical team
}
}
There are many ways to optimize these classes, like building in a switch that determines whether AllOrNone should be respected during DML. For now, we’ll just provide you with the skeleton, which we hope will already make your life a lot easier.