0

I tried to answer it below.

The program is as follows.

** BatchUpdateUser **

global without sharing class BatchUpdateUser implements Database.Batchable , Database.Stateful {

global String NAME = 'User';

global BatchUpdateUser() {
}

global Database.QueryLocator start(Database.BatchableContext BC) {
    return Database.getQueryLocator([
            SELECT
                    Id
                    , UserId__c
                    , UserRoleId__c
                    , ProfileId__c
                    , Group__c
                    , IsActive__c
            FROM Work__c
            WHERE
                    Name = :NAME
    ]);
}

global void execute(Database.BatchableContext BC, List<sObject> scope) {

    List<Work__c> works = (List<Work__c>) scope;

    List<String> userIds = new List<String>();
    for (Work__c w : works) {
        userIds.add(w.UserId__c);
    }

    List<User> users = [
            SELECT
                    Id
                    , UserRoleId
                    , ProfileId
                    , Group__c
                    , IsActive
            FROM User
            WHERE
                    Id = :userIds
    ];
    Map<Id, User> userMap = new Map<Id, User>(users);

    for (Work__c w : works) {
        userMap.get(w.UserId__c).UserRoleId = w.UserRoleId__c;
        userMap.get(w.UserId__c).ProfileId = w.ProfileId__c;
        userMap.get(w.UserId__c).Group__c = w.Group__c;
        userMap.get(w.UserId__c).IsActive = w.IsActive__c;
    }

    update userMap.values();

}

global void finish(Database.BatchableContext BC) {
}
}

** UserTrigger **

trigger UserTrigger on User (after insert, after update) {

Set<ID> trueIds = new SET<ID>();
for(User u : Trigger.new) {
    if(u.LandingGoalFlg__c == true) {
        trueIds.add(u.Id);
    }
}
if(Trigger.isInsert && Trigger.isAfter){
    if(trueIds.size() != 0) {
        LandingBusinessConditionHandler.OnAfterUpsertAsync(trueIds);
    }
} else if(Trigger.isUpdate && Trigger.isAfter){
    if(trueIds.size() != 0) {
        LandingBusinessConditionHandler.OnAfterUpsertAsync(trueIds);
    }
    }
}

** LandingBusinessConditionHandler **

Public class LandingBusinessConditionHandler {

    @future
    Public static void OnAfterUpsertAsync (Set <Id> newUserIds) {
        OnAfterUpsertAsync(newUserIds);
    }

    Public static void OnAfterUpsertSync(Set <ID> newUserIDs) {
        // do something
    }
}

However, I got an simller error

I would be pleased if you could point out the wrong part.

BatchUpdateUser bc = new BatchUpdateUser(); Database.executeBatch(bc);

  • You missed the very last part of the answer. You need two methods in the LandingBusinessConditionHandler class. One future that calls the main non future and one non future (main) that does the work. Call the non futur in your batch. You actually can't call a real future method from batch. – Eric May 14 '17 at 14:52
  • You can call the future from a scheduled class like you have done but not a batch – Eric May 14 '17 at 14:56
  • Not sure this is not a duplicate of https://salesforce.stackexchange.com/questions/24843/calling-future-method-from-batch but maybe there is something new in this question – Eric May 14 '17 at 15:05
  • @Eric thank you very much!! –  May 15 '17 at 05:43
  • Excuse me. Because English is weak, let me check. (1) BatchUpdateUser has not changed. (2) Change the method called by UserTrigger from OnAfterUpsertAsync to main. (3) The main method is a method that is not @future and calls the OnAfterUpsertAsync method. Is it OK like that? –  May 15 '17 at 12:42

1 Answers1

2

You need to call the correct method. Also no need for the duplicate logic branches

if(!trueIds.isEmpty()) {
    (system.isBatch() || system.isFuture()) ?
          LandingBusinessConditionHandler.OnAfterUpsertSync(trueIds) :  
          LandingBusinessConditionHandler.OnAfterUpsertAsync(trueIds);
}

Technically you should be checking the List in the handler class and return null if empty. As a general rule if the method you are calling require data integrity checks, those checks should be in the method itself and not left up to the calling method.

So the handler would be like

Public class LandingBusinessConditionHandler {

    @future
    Public static void OnAfterUpsertAsync (Set <Id> newUserIds) {
        OnAfterUpsertSync(newUserIds);
    }

    Public static void OnAfterUpsertSync(Set <ID> newUserIDs) {
        if(newUserIds == null || newUserIds.isEmpty()) return;
        // do something
    }
}

Then the trigger would simply be:

(system.isBatch() || system.isFuture()) ?
          LandingBusinessConditionHandler.OnAfterUpsertSync(trueIds) :  
          LandingBusinessConditionHandler.OnAfterUpsertAsync(trueIds);
Eric
  • 54,152
  • 11
  • 100
  • 195
  • +1, but a minor typo: future method is a recursive loop that'll crash when it tries to call itself. – sfdcfox May 17 '17 at 04:55
  • @sfdcfox fixed. Nice catch – Eric May 17 '17 at 04:57
  • @Eric Even with the code I taught, the future method could not be executed from the same batch and it could not be resolved. I will consider another method. Thank you very much. –  May 17 '17 at 05:20