[SalesForce] Error in using multiple @future methods

I have created a Trigger and Class that works in dev environment, but I'm trying to deploy it to production, and it is testing all of my code with all the other classes in the environment. The only error that is caused in the other unrelated classes that are tested, occurs because of Line 9 of my class, involving the Account. What I am trying to do is whenever a user is created, a contact is created with that user's info, and is placed into the 'Company Users' account. It has something to do with my @future methods, and how it's trying to add the contacts to the specific account, while the other @future methods are still running. If anyone has any ideas on how I can resolve this, I would be so thankful.

The @future mechanism is being used to avoid the problem of sObjects That Cannot Be Used Together in DML Operations, in this case the User object.

Trigger

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

    if(Trigger.isAfter && Trigger.isInsert){
        Set<ID> usrIds = new Set<ID>();

        for(User u : Trigger.new){
            usrIds.add(u.id);
        }

        UserContactSyncClass1.syncContact(usrIds);  
    }

    if(Trigger.isAfter && Trigger.isUpdate){ 
        Set<ID> idSet = new Set<ID>();
        Set<ID> idSet2 = new Set<ID>();

        for(User u : trigger.new){
            for(User r : trigger.old){
                if(u.ID == r.ID && u.isActive==false && r.isActive==true){
                    idSet.add(u.ID);
                }

                if (u.ID == r.ID && u.isActive==true && r.isActive==false) {
                    idSet2.add(u.ID);
                } 
            }
        }

        UserContactSyncClass1.InactiveContact(idSet);
        UserContactSyncClass1.ActivateContact(idSet2);
    }

}

Class

public class UserContactSyncClass1 {

    @future
    public static void syncContact(Set<Id> userSet){
        List<Contact> cList = new List<Contact>();
        List<User> userList = [Select ID, FirstName, LastName, Email, Phone, True_Market_Unit__c, True_Market_Name__c FROM User WHERE ID IN :userSet];
        List<String> mList = new List<String>();
        List<String> mktList = new List<String>();
        **Account acc = [SELECT Id, Name FROM Account WHERE Name = 'Company Users' Limit 1];**

        // Loop through Users, to get List of True Market Number
        for(User usr : userList){
            mList.add(usr.True_Market_Unit__c);        
        }

        // Use the True Market Number and Match to the True Market Number in Markets
        for(Market__c mkt : [SELECT Name, Unit_Number__c FROM Market__c WHERE Unit_Number__c IN :mList]){
            mktList.add(mkt.Name);  
        }

        for(User usr : userList){
            for(String mk : mktList){
                usr.True_Market_Name__c = mk;
            }
            System.debug(usr.True_Market_Name__c);
        }

        for(User u : userList){
            cList.add(new Contact(FirstName=u.FirstName, LastName=u.LastName, User_True_Market_Name__c=u.True_Market_Name__c, Email=u.Email, Phone=u.Phone, OwnerID=u.ID, Related_User__c=u.Id, AccountId = acc.Id));
        }

        upsert cList;
    }

   @future
    public static void InactiveContact(Set<ID> userSet){
        List<Contact> cList = [Select ID, Contact_Status__c FROM Contact WHERE Related_User__c = :userSet];

        for(Contact c : cList){
            c.Contact_Status__c = 'Inactive';
        }

        update cList;
    }

    @future
    public static void activateContact(Set<ID> userSet){
        List<Contact> cList = [Select ID, Contact_Status__c FROM Contact WHERE Related_User__c = :userSet];

        for(Contact c : cList){
            c.Contact_Status__c = 'Active';
        }

        update cList;
    }

}

Best Answer

Given that the reason to use a future method is to separate the DML of the User from the DML of the Contact object, only one of these DML needs to move to a future method. The change driving the process is the change to the User, so it seems natural to keep that logic synchronous and move the change to the Contact out into the future: Contact will always follow User.

So if I was writing this, I would use this trigger:

trigger UserInsertUpdate on User (before insert, after insert, after insert,after update) {

    if (Trigger.isBefore) {
        // Update market name to match market unit on the User.
        // As this is a before trigger no update is needed.
        // A trigger on Market__c would also be needed if the
        // Name/Unit_Number__c combinations ever change.
        Map<String, String> m = new Map<String, String>();
        for (User u : Trigger.new) {
            if (u.True_Market_Unit__c != null) {
                m.put(u.True_Market_Unit__c, null);
            }        
        }
        for (Market__c market : [
                SELECT Name, Unit_Number__c
                FROM Market__c
                WHERE Unit_Number__c IN :m.keySet()
                ]){
            m.put(market.Unit_Number__c, market.Name);
        }
        }
        for (User u : Trigger.new) {
            u.True_Market_Name__c = m.get(u.True_Market_Unit__c);  
        }
    } else {
        // Has to be in a different transaction to avoid a mixed DML exception.
        SyncContactsToUsers.sync(Trigger.newMap.keySet());
    }
}

and this class:

public class SyncContactsToUsers {

    public class SyncContactsToUsersException extends Exception {
    }

    @future
    public static void sync(Set<Id> userIds) {

        // Find existing related Contacts (if any)
        Map<Id, Contact> m = new Map<Id, Contact>();
        for (Contact c : [
                select Id, Related_User__c
                from Contact
                where Related_User__c in :userIds
                ]) {
            m.put(c.Related_User__c, c);
        }

        // Update existing or insert new Contacts with values consistent with User
        Id accountId = queryAccountId();
        Contact[] upserts = new Contact[] {};
        for (User u : [
                select FirstName,LastName,True_Market_Name__c,Email,Phone,Id,isActive
                from User
                where Id in :userIds
                ]) {
            Contact c = m.get(u.Id);
            if (c == null) {
                c = new Contact(Related_User__c = u.Id);
            }
            c.FirstName = u.FirstName;
            c.LastName = u.LastName;
            c.User_True_Market_Name__c = u.True_Market_Name__c;
            c.Email = u.Email;
            c.Phone = u.Phone;
            c.OwnerID = u.Id;
            c.AccountId = accountId;
            c.Contact_Status__c = u.isActive ? 'Active' : 'Inactive';
            upserts.add(c);
        }
        upsert upserts;
    }

    private static queryAccountId() {

        // Find the Account or insert it if its missing
        final String accountName = 'Company Users';
        Account[] accounts = [
                SELECT Id
                FROM Account
                WHERE Name = :accountName
                ];
        Id accountId;
        if (accounts.size() == 1)  {
            return accounts[0].Id;
        } else if (accounts.size() == 0) {
            Account a = new Account(Name = accountName);
            insert a;
            return a.Id;
        } else {
            // Don't want to attach Contacts to the wrong Account.
            // If more than 1 don't know which one to use.
            throw new SyncContactsToUsersException(''
                    + 'More than one Account with name '
                    + accountName
                    + '; contact your System Administrator to address'
                    );
        }
    }
}

Both probably contain typos.

If the above code still results in "ENTITY_IS_LOCKED" DmlExceptions, then moving to using the Queueable mechanism instead of the future mechanism is probably going to be necessary so you can catch the exception and retry. (You can't kick-off a new future method from a future method.) Skipping work if you are already in a future context won't work as legitimate updates of unrelated objects could get dropped.

Related Topic