1

Getting an error with trigger/class.

We have an automated process the creates a user record (from our AD/SSO process). When a User is created, a trigger sends an email to our support group to finalize some manual setup steps, but one of those manual steps is to create a Contact record and relate the User to it. I'm trying to add to our trigger to also create and relate the contact record. However, when it fires, I get the following error.

Error: Invalid Data. Review all error messages below to correct your data. Apex trigger NewUserCreatedTrigger caused an unexpected exception, contact your administrator: NewUserCreatedTrigger: execution of AfterInsert caused by: System.DmlException: Insert failed. First exception on row 0; first error: MIXED_DML_OPERATION, DML operation on setup object is not permitted after you have updated a non-setup object (or vice versa): Contact, original object: User: []: Class.NewUserCreatedNotification.createUserContact: line 32, column 1

Here's the TRIGGER:

trigger NewUserCreatedTrigger on User (after insert) {
    user[] users = trigger.new;

    NewUserCreatedNotification.sendEmail(users);
    NewUserCreatedNotification.createUserContact(users);
}

And here's the CLASS:

public class NewUserCreatedNotification  {
    public static void sendEmail(user[] newUser){
        for (user u : newUser){
            Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
            String[] toAddresses = new String[] {'[email protected]'};
            mail.setToAddresses(toAddresses); 
            mail.setSenderDisplayName ('Salesforce Support');
            mail.setSubject('A New Salesforce User has been created in ORG-2'); 
            mail.setPlainTextBody(GetMessageBodyText(u.ID)); 
            Messaging.sendEmail(new Messaging.SingleEMailMessage[]{mail});
        }
    }

    public static void createUserContact(user[] newUser){    
        for (user u: newUser){        
            account myFirm = [Select ID from account where name = 'CorpName'];
            Contact userContact = new Contact(
                user__c = u.ID,
                AccountID = myFirm.id,
                FirstName = u.firstname,
                Lastname = u.lastname,
                Title = u.title,
                Department = u.department,
                Phone = u.Phone,
                email = u.email,
                MailingStreet = u.street,
                MailingCity = u.city,
                MailingState = u.state,
                MailingPostalCode = u.postalcode,
                MailingCountry = u.country);
                insert userContact;

        }
    }

    public static string GetMessageBodyText(Id userID){
        user q = [select id, name, firstname, lastname, ProfileID, email,UserRoleID, userrole.name, profile.name, CreatedDate,LastLoginDate,FederationIdentifier from user where id =: userID];
        return ' A new user record for '+ q.name +', has been created in ORG-2.\n Please confirm that this user has the correct security and Role settings.\n\n Name:  '+
                q.name+'\n Email Address:  '+
                q.Email+'\n Profile:  '+
                q.Profile.Name+'\n Role:  '+
                q.UserRole.Name+'\n Federation ID:  '+
                q.FederationIdentifier+'\n Created Date:  '+
                q.CreatedDate;      
    }
}

The email sends fine if I remove the Contact part, so I know that's not it. Thoughts?

2 Answers 2

2

As the MIXED_DML_OPERATION implies, you can't simply create a user (or other "Setup" type objects), and contacts (or other non-"Setup" type objects) in the same transaction. The usual way to avoid this is to make the function asynchronous.

@future public static void createUserContact(Set<Id> newUserIds){

Note that you can only send "primitives" through a future method, so you'll have to change your trigger slightly:

NewUserCreatedNotification.createUserContact(Trigger.newMap.keySet());

Also, your function doesn't support mass imports of users, so you might want to fix that.

6
  • 1
    You could also pass the user as a serialized JSON string if needed
    – Eric
    Commented Jul 20, 2015 at 19:50
  • 1
    @Eric True enough, but that's usually overkill. Yes, you need to do an extra query, but since it's a different context, it usually doesn't matter.
    – sfdcfox
    Commented Jul 20, 2015 at 20:06
  • Hmmm. I'm not very familiar (yet) with future methods, so that would explain my confusion. What's going on with your trigger? That's all new code to me. Thanks @sfdcfox.
    – HomerJ
    Commented Jul 20, 2015 at 20:14
  • @sfdcfox - Yup just adding another option for those interested
    – Eric
    Commented Jul 20, 2015 at 20:15
  • @HomerJ The @future annotation simply delays the function call to a different transaction (i.e. sometime after the user records have been saved). There's limitations on what you can do with this, so i encourage you to take a good read, but really you just need to know that they run "after" the primary transaction that called them.
    – sfdcfox
    Commented Jul 20, 2015 at 20:33
0

Just following up on this question. Here's the final version of my effort (minus test classes . . .)

Trigger

trigger NewUserCreatedTrigger on User (after insert) {
    user[] users = trigger.new;
    NewUserCreatedNotification.sendEmail(users);
    NewUserCreatedNotification.createUserContact(Trigger.newMap.keySet());
}

Class

public class NewUserCreatedNotification  {

    public static void sendEmail(user[] newUser){
        for (user u : newUser){
            Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
            String[] toAddresses = new String[] {'[email protected]'};
            mail.setToAddresses(toAddresses); 
            mail.setSenderDisplayName ('Salesforce Support');
            mail.setSubject('A New Salesforce User has been created in ORG-2'); 
            mail.setPlainTextBody(GetMessageBodyText(u.ID)); 
            Messaging.sendEmail(new Messaging.SingleEMailMessage[]{mail});
        }
    }

    public static string GetMessageBodyText(Id userID){
        user q = [select id, name, firstname, lastname, ProfileID, email,UserRoleID, userrole.name, profile.name, CreatedDate,LastLoginDate,FederationIdentifier from user where id =: userID];
        return ' A new user record for '+ q.name +', has been created in ORG-2.\n Please confirm that this user has the correct security and Role settings.\n\n Name:  '+
                q.name+'\n Email Address:  '+
                q.Email+'\n Profile:  '+
                q.Profile.Name+'\n Role:  '+
                q.UserRole.Name+'\n Federation ID:  '+
                q.FederationIdentifier+'\n Created Date:  '+
                q.CreatedDate;      
    }


@future
public static void createUserContact(set<ID> recordIDs){    
        list<user> users = [Select ID, Firstname, Lastname, title,department,phone, email,street,city,state,postalcode,country from user where id IN :recordIDs];
        for (user u: users){        
            account myFirm = [Select ID from account where name = 'CorpName'];
            Contact userContact = new Contact(
                user__c = u.ID,
                AccountID = myFirm.id,
                FirstName = u.firstname,
                Lastname = u.lastname,
                RecordtypeID = '012o0000000Qw66',
                Title = u.title,
                Department = u.department,
                Phone = u.Phone,
                email = u.email,
                MailingStreet = u.street,
                MailingCity = u.city,
                MailingState = u.state,
                MailingPostalCode = u.postalcode,
                MailingCountry = u.country
                );
                insert userContact;

        }
    }
}

Any additional comments are appreciated.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .