4

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;
    }

}
7
  • Using multiple future calls is likely to make your code unreliable because exactly when and in what order the work is done is unpredictable - see e.g. How far in the future are future annotated methods executed?. It may not be the cause of your present problem, but I would suggest removing the future annotations to make your code determinate. Quote: "there is no SLA on asynchronus future calls".
    – Keith C
    May 18 '15 at 20:11
  • I need the future calls because I need the user to be inserted, and then the contact to be inserted within that user after. How would you recommend I work around the multiple future calls option?
    – Stephen
    May 18 '15 at 20:15
  • @Keith C is right. The After Insert part of the User trigger will ensure that the user is in the database. It should not need to be a future call. After insert/update triggers mean the following: The changes/new records as part of this trigger have been applied in this execution context. It's not yet committed (so other users can't see it yet) but any queries for records within the trigger itself or any called classes will see these changes/inserts and can link to them as needed. Looking at the code, it should all work synchronously. Try removing the future context. May 18 '15 at 20:38
  • If I take out the future methods, then I get a Mixed DML Exception. I've been battling this problem for days, so thank you both for helping.
    – Stephen
    May 18 '15 at 20:47
  • 1
    It looks like you also have a couple of logical issues: you never update this change usr.True_Market_Name__c = mk; each time the User object is updated a new Contact will be inserted as you are missing a query for the existing Contacts related to the Users that I presume you should be updating.
    – Keith C
    May 18 '15 at 22:31
2

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.

2
  • I initially thought this fixed my issue, but it turns out I'm getting the same error. Because we are querying for the account to put in the contact AccountID field in the future call, it is causing errors with other classes in my environment. As a result, all of the errors in the other classes are saying "Account doesn't exist". But since we have Contact in the future call, and AccountID is within the Contact, I keep running into an issue. Do you have any work arounds that I could use? Do you think queueable would be a solution to this particular problem?
    – Stephen
    May 19 '15 at 17:55
  • @Stephen I'm afraid I don't understand what is going on with the error you are seeing. When the Account has been created it should never be re-created and should always be visible in the other classes. In my comment about Queueable I was thinking of situations where separate future transactions might (very rarely) be trying to update the same Contact at the same time.
    – Keith C
    May 19 '15 at 21:03
1

Line 9 failure of your class would have to do with setting up test data in your test classes. If you are not inserting an account with the name 'Company Users' in your testing, then line 9 will definitely fail. That's the only reason that I can think of that line 9 would likely fail.

Account acc = [SELECT Id, Name FROM Account WHERE Name = 'Company Users' Limit 1];

In your dev environment it's no problem because "running all tests" is not required and it would not happen on it's own. I bet if you run all tests in your dev environment you would get the same error.

I would suggest that you find your test data setup code blocks, and ensure that anywhere a User is being created in test, you create the account with name 'Company Users'.

Here is a link to useful information on this: http://jessealtman.com/2013/09/proper-unit-test-structure-in-apex/ scroll to "Setting up the Data Model"

2
  • I made sure that my test data does create an account with name 'Company Users'. It doesn't seem to be a test class problem. It seems to be something involving the future methods, and how because of the time delay on when each of the future methods fire, it is causing a backup with my other classes data with the Accounts that they are creating as well. If that makes sense.
    – Stephen
    May 18 '15 at 20:06
  • Is parallel test execution enabled? The test methods should not have access to each others test data.... but weirder things have happened. You can disable this and try again, this would ensure that the tests run one at a time. This will also be slower, maybe much much slower if you have hundreds of thousands of lines of code or into the millions. A future call within the bounds of test execution is forced to run at the very end of the test method, or on the line with test.stoptest. Line 9 failure is a test data setup issue within the single test method, not related to the others. May 18 '15 at 20:29
1

Two ways to solve this

  1. Find the other future methods that are invoking your future methods and see if you can refactor them to combine the work.

  2. Simply wrap your code that creates the contact in a test like:

    if (!system.isFuture()){
        UserContactSyncClass1.syncContact(usrIds);
     }
    

If you wrap it this way, the creation of a User WILL cause the contact to be created but your code will NOT be executed if a subsequent future method executes the account trigger.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.