Sdry, I have wanted to do exactly this ever since Custom Settings came out. The problem, though, is that there is no Apex API for Custom Labels (click the link to vote for the idea for it!), so Custom Labels cannot be dynamically retrieved or requested within Apex. However, it CAN, as of Spring 12, be done in Visualforce, using Dynamic Components.
Here, I think is what you are looking for in Apex (that cannot yet be done):
Custom Settings + Custom Labels IN APEX (**not yet possible**)
// Get the stored Custom Setting record for User Preferences
User_Prefs__c p = User_Prefs__c.getInstance(UserInfo.getUserId());
// Get the stored ISO Code
String isoCode = p.ISO_Code__c;
// Get the running user's language
String languageKey = [
select LanguageLocaleKey from User
where Id = :UserInfo.getUserId()
limit 1
].LanguageLocaleKey;
// Get the translation of the ISO Code corresponding to this user's language
// **THIS IS NOT POSSIBLE YET IN APEX**
String translatedCode = System.Label.getLabel('ISOCode_'+isoCode).getTranslation(languageKey);
And here would be an equivalent if you are using Visualforce:
Custom Settings + Custom Labels IN VISUALFORCE (possible right now)
/* APEX CONTROLLER */
public class Controller {
public String getStoredISOCode() {
// Get the stored Custom Setting record for User Preferences,
// and return the ISO code
return User_Prefs__c.getInstance(UserInfo.getUserId()).ISO_Code__c;
}
}
/* VF PAGE */
<apex:page controller="MyController">
<b>ISO Code</b>{!storedISOCode}
<b>Translated</b>{!$Label['ISOCode_' & storedISOCode]}
</apex:page>
I just tested this, there is a caveat, but it does work.
First, some code:
global class LongRunningBatch implements Database.Stateful, Database.Batchable<Account>, Iterable<Account>, Iterator<Account> {
Integer subcounter = 0, counter = 0, max = 0;
global LongRunningBatch(Integer total) {
max = total;
}
global Iterable<Account> start(Database.BatchableContext bc) {
return this;
}
global Iterator<Account> iterator() {
return this;
}
global Boolean hasNext() {
return counter < max;
}
global Account next() {
return new Account(Name=String.valueOf(++counter));
}
global void execute(Database.BatchableContext bc, Account[] records) {
if(Settings__c.getInstance('batch')!=null && Settings__c.getInstance('batch').Value__c == 'STOP') {
// counter = max;
// Above didn't stop my batch. *sigh*
// But at least it returns early.
return;
}
SavePoint sp = Database.setSavePoint();
try {
insert records;
delete records;
} catch(exception e) {
}
Database.rollback(sp);
subcounter = counter;
}
global void finish(Database.BatchableContext bc) {
Messaging.SingleEmailMessage message = new Messaging.SingleEmailMessage();
message.setSubject('Finished processing');
message.setPlainTextBody('Finished processing with '+String.valueof(subcounter)+' records processed, '+string.valueof(counter)+' records queued.');
message.setTargetObjectId(UserInfo.getUserId());
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { message });
}
}
Let me explain the original rationale:
I thought the system scheduler code did something like this:
SObject[] records = new SObject[0];
while(iterator.hasNext()) {
records.add(iterator.next());
if(records.size()==scope) {
execute(records);
records.clear();
}
}
Unfortunately, it does not. It does something more like this:
SObject[] allrecords = new SObject[0], thisBatch = new SObject[0];
while(iterator.hasNext()) {
allRecords.add(iterator.next());
}
while(!allRecords.isEmpty()) {
thisBatch.add(allrecords.remove(0));
if(thisbatch.size()==scope) {
execute(context, thisbatch);
thisbatch.clear();
}
}
This is obviously an over-simplification, but for the purposes of what's going on here, it defines the logic we're working with. The design means that there's a practical upper limit to how many items you can process with an Iterable class (I believe it caps out around 500,000 for simple iterators).
At any rate, given this in mind, I kicked off the code with the following statement:
LongRunningBatch b = new LongRunningBatch(250000);
Database.executeBatch(b);
Here's a profiling log without the setting in place:
08:57:24:000 CUMULATIVE_PROFILING External entry point: global void execute(Database.BatchableContext, LIST<Account>): executed 1 time in 1302 ms
Here's a profiling log with the setting in place:
08:57:26:000 CUMULATIVE_PROFILING External entry point: global void execute(Database.BatchableContext, LIST<Account>): executed 1 time in 14 ms
The logs also indicate that no DML actions occurred while the batch setting was configured. In my Developer Console window, you can see it take place immediately:
Batch Apex 06:57:24 ...
/setup/ui/editCustomSettingsData 06:57:25 ...
Batch Apex 06:56:26 ...
Those debug lines from above are the from the log files immediately before and after the change in custom settings. The effect was visible in a single batch.
That being said, the batch can't fully abort itself early, but in the event of a long running batch, it can reduce its execution time significantly. You should note that there appears to be a significant delay between batch executions (because it is a shared resource), so the batch may only finish marginally faster if it is processing a large number of small transactions, as opposed to large transactions (e.g. if your execution time is less than 10k lines of code and/or 2 seconds of execution, there's probably no reason to implement a kill-switch like this).
Best Answer
I'd say it depends on how you are using the
Map<String, CustomSetting__c>
thatgetAll()
returns and how may list records there are.If you are looping over the map
values()
searching for a single record based on a field other than the Name then SOQL would probably serve you better as you could just grab the required records.One thing to note with the SOQL approach. It will cost you one of your 100 synchronous SOQL queries governor limit. Where as the cached
getAll()
is free from that but may not have the absolute latest data.I assume that the caching is only for the duration of the transaction (TODO: confirm this is actually the case).See the comment from @ca_peterson that the underlying implementation is based on memcached.If you can access the Map by the Name key then the Map has some good advantages over SOQL if you need to pick out several values. Even better would be pulling out individual CustomSetting__c records by name using
getInstance(dataset_name)
I'm going to go out on a limb here and say that the cache will be
muchfaster than a SOQL query in getting a raw list of all the possible list values. Of course, it's hard to say what other caching Salesforce has got going on internally.I did a quick test with a list custom setting to get a list of all values:
There is a slight advantage to the cache here, but that doesn't take into account how much extra processing you will need to do on the results.
So, in conclusion, it depends on how many of those list custom setting values you want and if you can access them by name. You will need to try both approaches on your data to see which works best while taking into account if you can spare the extra SOQL query.