[SalesForce] Mixed DML Operation Error when Trying to Insert Contact

The basic premise of this code, for those of you who have not seen my 600+ other posts about it, is that it creates a Contact whenever someone creates a User. Certain fields of the contact are then automatically populated using the same fields from the User.

I thought I finally finished this code, when I got the Mixed DML Operation error after some more extensive testing. When I create a new user with a "Chatter Free" license, no Role, and the "Chatter Free User" profile, it goes through perfectly, even brought a tear to my eye. However, when I create a User with any other license, a role, and any other profile, I get the above error (this brought many more tears to my eye).

I'm not sure if I actually need a workaround, or if there is some small, stupid problem with my code. Hopefully someone here can offer some insight. I tried using a @future class, as well as system.runas (Sorry about all the comments, I'm very new to this and that's how I keep my thought process straight while learning).

here is my CLASS

public class UserContact {   
//Create the class and grab the IDs needed
 public static void createUserContact(set<ID> recordIDs){
//Grab all the required fields from user, essential to test class

    list<user> users = [SELECT ID, Firstname, Lastname, email, name, username, UserRoleId, CommunityNickname, TimeZoneSidKey, LocaleSidKey, EmailEncodingKey, ProfileId, LanguageLocaleKey 
                        FROM user
                        WHERE ID in :recordIDs];

        String accountName = 'Company Internal';
        //In this query you want to "LIMIT 1" to avoid more than 
        //1 record returning
        Account Accountx = [SELECT ID
                            FROM account
                             Where Name = :accountName
                            LIMIT 1];

    //Create a list to hold the contacts being created
    List<Contact> contactsToCreate = new List<Contact>();

     //Iterate through each field we want to automatically populate
    for (user x: users){

        //Assign the email related to the user grabbed to a string so 
        //we can reference it later.
        //If this was not done, we would get an error for trying to 
        //mix an SObject with a String.
        String userEmail = x.Email;

        //If statement containing the contact creation.  If 
        //the parameters are not met, the user is still
        //created, but a contact is not.
        if(userEmail.endsWith('@company.com')){
        Contact userCon = new contact(
        AccountID = AccountX.id,    
        FirstName = x.FirstName,
        LastName = x.LastName,
        Username__C = x.Username,
        email = userEmail);
        contactsToCreate.add(userCon);
        insert contactsToCreate;                
        }
        else{
            system.debug('Email not internal, Contact not created');
        }}}}

Here is what I tried with the @Future method. The error I got was… "Variable does not exist: recordIDs" I played around with this for quite a while, so this example may not be the closest I got. If I remember correctly, my problem was referencing the lists I got in the first class.

public class UserContact {   
//Create the class and grab the IDs needed
 @future public static void createUserContact(set<ID> recordIDs){
//Grab all the required fields from user, essential to test class

    list<user> users = [SELECT ID, Firstname, Lastname, email, name, username, UserRoleId, CommunityNickname, TimeZoneSidKey, LocaleSidKey, EmailEncodingKey, ProfileId, LanguageLocaleKey 
                        FROM user
                        WHERE ID in :recordIDs];

        String accountName = 'Company Internal'; //This is where you will provide your name (could be any other field)
        //In this query you want to "LIMIT 1" to avoid more than 1 record returning
        Account Accountx = [SELECT ID
                            FROM account
                             Where Name = :accountName
                            LIMIT 1];
public class insertContactClass{
    public void insertContactMethod(){
    //Create a list to hold the contacts being created
    List<Contact> contactsToCreate = new List<Contact>();

    UserContact.createUserContact(recordIDs);

     //Iterate through each field we want to automatically populate
    for (user x: users){
        String userEmail = x.Email;
        if(userEmail.endsWith('@company.com')){
        Contact userCon = new contact(
        AccountID = AccountX.id,    
        FirstName = x.FirstName,
        LastName = x.LastName,
        Username__C = x.Username,
        email = userEmail);
        contactsToCreate.add(userCon);
        insert contactsToCreate;                
        }
        else{
            system.debug('Email not internal, Contact not created');
        }}}}}

Here is the TRIGGER:

trigger NewUserCreatedTrigger on User (after insert) {
    //Creates an array to hold the users caused by trigger firing
    user[] users = trigger.new;
    //Creates a map of records being inserted, then keyset returns the IDs for all records in the map
    UserContact.createUserContact(Trigger.newMap.keySet());
    }

Best Answer

So this is the code may work

Future method should work for DML error.

Trigger

trigger NewUserCreatedTrigger on User (after insert) {
    //Creates an array to hold the users caused by trigger firing
    user[] users = trigger.new;
    //Creates a map of records being inserted, then keyset 
    //returns the IDs for all records in the map
    UserContact.createUserContact(Trigger.newMap.keySet());
}

Handler

public class UserContact {   
    //Create the class and grab the IDs needed
     @future 
     public static void createUserContact(set<ID> recordIDs){
    //Grab all the required fields from user, essential to test class

        list<user> users = [SELECT ID, Firstname, Lastname, email, name,
                                   username, UserRoleId, CommunityNickname, 
                                   TimeZoneSidKey, LocaleSidKey, EmailEncodingKey, 
                                   ProfileId, LanguageLocaleKey 
                              FROM user
                             WHERE ID in :recordIDs];
        //This is where you will provide your name (could be any other field)
        String accountName = 'Company Internal'; 
        //In this query you want to "LIMIT 1" to avoid more than 1 record returning
        Account Accountx = [SELECT ID FROM account
                             Where Name = :accountName LIMIT 1];
        List<Contact> contactsToCreate = new List<Contact>();

         //Iterate through each field we want to automatically populate
        for (user x: users){
            String userEmail = x.Email;
            if(userEmail.endsWith('@company.com')){
            Contact userCon = new contact(
            AccountID = AccountX.id,    
            FirstName = x.FirstName,
            LastName = x.LastName,
            Username__C = x.Username,
            email = userEmail);
            contactsToCreate.add(userCon);

            }
            else{
                system.debug('Email not internal, Contact not created');
            }
        }
        insert contactsToCreate;//make DML outside of for loop 
    }
}
Related Topic