(1) To determine the number of Future calls run in the past 24 hours, you can use this utility method:
public static Integer GetNumFutureCallsInLast24Hours() {
return [select count() from AsyncApexJob
where CreatedDate >= :Datetime.now().addHours(-24)
and JobType = 'Future'];
(2) To dynamically run methods in a future
context ONLY if you have future
calls available, you could leverage a "dispatcher" method framework that would run your code in a future
context only if you have not surpassed the 24-hour-calls Limit and Call Stack Limit yet:
// Can we run future calls right now?
public static boolean CanUseFutureContext() {
boolean callStackLimitExceeded = Limits.getFutureCalls() >= Limits.getLimitFutureCalls());
if (!callStackLimitExceeded) {
// Check 24-hour rolling window limit
// of 200 * # of Salesforce, Salesforce Platform, or Force.com One App Licenses
// *NOTE 1*: These queries may be cacheable depending on your situation
// *NOTE 2*: If you have not used up all of your allocated licenses,
// you will have more available licenses than this, so you may need to adjust.
// However, you can't use SOQL to determine
//how many AVAILABLE licenses you have,
// only how many have actually been assigned to users
return GetNumFutureCallsInLast24Hours() < getMaxFutureCallsAllowed();
} else return false;
}
public static Integer getMaxFutureCallsAllowed() {
Integer usersCount = [
SELECT
COUNT()
FROM
User
WHERE
Profile.UserLicense.LicenseDefinitionKey IN ('SFDC','AUL','PID_FDC_FREE')];
return Math.max(250000, usersCount*200);
}
// Main entry point to your method: call this from a trigger, other class, etc.
public static void RunMyLogic(params) {
if (CanUseFutureContext()) futureRunMyLogic(params);
else RunMyLogicMain(params);
}
private static void RunMyLogicMain(params) {
/* Your logic here */
}
@future
private static void futureRunMyLogic(params) {
RunMyLogicMain(params);
}
If you want to execute the same logic for 16 different fields then obviously it would be best to write the algorithm once. And in Salesforce bulking up any database reads and writes is important too. So one trigger that deals with multiple Accounts and handles all 16 fields would be ideal. Restructuring your code now is likely to avoid a lot of future pain.
To execute Apex logic in such a trigger, the get
and put
methods that take the field name string can be used:
List<Xyz__c> updates = new List<Xyz__c>();
Set<String> fields = new Set<String>{'UK_Rep__c', 'FR_Rep__c'', ...};
for (Account a : Trigger.new) {
// Repeat algorithm for each of the 16 fields
for (String field : fields) {
Id repId = (Id) a.get(field);
...
}
}
update updates;
And where you need the query to vary a lot, the query can be created as a string and executed as Dynamic SOQL. (Or sometimes just a where abc in :set
may suffice.)
Also note that the sort of code shown immediately below is generally lethal (= governor limit exception). Fine with 10 Accounts where on average 10x10/2=50 times round the inner loop, but with 1,000 accounts its 1,000x1,000/2=500,000.
for(Account a:accounts){
for(Account old : Accountsold)
{
if (old.id == a.id)
{
Pass Trigger.oldMap
into the class to remove the exponential factor:
for(Account a : accounts) {
Account old : oldAccountsMap.get(a.Id);
Having read through your question and comments I still don't understand why @future is being used. What goes wrong if it is removed?
@Kevin C - The only way StackExchange is allowing me to reply is by editing your Answer. Again I apologize for not using the site as intended and will visit the help center when I have a moment.
First, thank you for your helpful response. To answer your question, the following scenario will occur if @future is removed.
-If John D is the Account Owner and I attempt to add him to one of the Team Role fields on the Account then I receive an error. I do not have the exact error handy, but it is basically saying that I cannot perform the function because I am attempting to give John D lower permissions then what he already has. Due to him being the Account Owner he has 'Full'Access, but my code is trying to give him 'Read/Write'. I would be fine with 'Full Access' but the value is not available in Apex. @future allows the Account Owner to be added as a Team Member when adding him to the custom fields.
Again, thank you for your response. Like I mentioned, I am VERY green when it comes to APEX and really trying to get a handle on it. I will take a closer look at what you provided and see if I can piece it all together.
Best Answer
The governor limit is that no more than 10 future calls may be made in a single request so your trigger is broken as soon as more than 10 Case objects are updated at once that match you
if
condition.If you want to use a future method call it once and pass it the set of case IDs i.e. collect the set if IDs first then make a single call to the future method.
Add debugging to your trigger to see how many Case objects are coming through at once if you are not sure. But the trigger needs bulkifying anyway.
Note that if you are using this sort of code in other triggers and those triggers run as part of the same request their future method calls will also be counted so they all need bulkifying.
PS
Noticed your trigger has an unnecessary nested loop pattern. This would be a typical way to accomplish what I think you are trying to do:
PPS
If the web service API you are calling only allows a request for a single Case to be made at once (and can't be changed) you can use a
Batchable
marked withDatabase.AllowsCallouts
and set the batch size to 10 instead of the future. But there are governor limit traps there too to watch out for (that will be eased when the "Apex Flex Queue" eventually becomes generally available).