function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
brianspatsobrianspatso 

Using @future to overcome Mixed_DML_Operation error to update Users from list of Contact Owners

I am fairly new to Apex, and I'm trying to update a list of Contacts whenever something is changed in the parent Account. The Trigger I have written works, apart from where the Contact Owner is Inactive.

 

I could transfer the Contacts to another, active Owner and update them. But for Opportunities, I need to put the old Owner back. So my idea is to activate the Owner, update the Contact, then deactivate the Owner.

 

However, everything I try generates the Mixed DML Operation error, because I am trying to update both the Contact and User objects. I have researched this problem and found the following. It mentions that I can use the @future method inside a class, then call the class from the trigger.

 

However, I don't know where to start with this. My experience with classes is around test classes - so I'm not sure how to use a class to actually update a record permanently.

 

This is the code I have to update the Contacts:

trigger UpdateChildObjectsToDepartment on Account (after update) {

  Map<Id, Account> accountMap = new Map<Id,Account>();
  Account parent;

//For changing company TO Deparment
  for (Integer i = 0; i < Trigger.new.size(); i++){
    if (Trigger.new[i].Department__c == true && Trigger.old[i].Department__c == false) {
      accountMap.put( Trigger.new[i].Id, Trigger.new[i]);
    }
  }
  
 
  if( accountMap.size() > 0 ) {
  Set<Id> accountIds = accountMap.keySet();
  
  List<Contact> relatedContacts =
      [SELECT f.id, f.Accountid,  f.Owner.Name, f.Department__r.id, f.Ownerid, f.Owner.IsActive
      FROM Contact f
      WHERE f.Accountid IN :accountIds
      FOR UPDATE];
      
  for ( Contact child : relatedContacts ) {
        if ( accountMap.containsKey( child.Accountid ) ) {
        parent = accountMap.get( child.Accountid );
        child.Department__c = child.Accountid;
        child.Accountid = parent.Parent_Company__c;
        }
      }
      
      
update relatedContacts;
   
}}

 

Best Answer chosen by Admin (Salesforce Developers) 
brianspatsobrianspatso

I've found a solution to this, which is difficult to explain, but I'll try.

 

My original idea was to have two fields on the Deal: a Parent Company and a Department. When the companies were updated or changed, the code I wrote was trying to swap them around in the child records.

 

However, the big problem with this was that I couldn't re-parent a child record when the owner was inactive.

 

My new idea changes it slightly. There is now only one field on the Deal: a Company field and this field can be either a Parent Company or a Department. There is also a second, hidden field which is called Parent Company. If the Company is a parent, then the Company name gets copied into Parent Company. If the Company is a department, then its parent will get copied into the Parent Company.

 

The advantage of this setup is that when changes are made, the trigger only needs to update the Parent Company field on the Deal. And since this field is a custom lookup, we can update it, even though the owner is inactive.

 

I believe this will work, although feel free to tell me otherwise!

All Answers

Shashikant SharmaShashikant Sharma

Yes you are right you need to use future method

 

See this how to use future

http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_classes_annotation_future.htm

 

If you have any problem implementing it, pleaseask.

 

From you code I did not get where are you updating user, is it in contacts trigger.

I want to know the sequeance of update.

brianspatsobrianspatso

HI Shashikant ,

 

Thanks for the reply.

 

I haven't added in the User update to this code yet, as I wasn't sure how to do it.

 

I think I need to activate the Owner, update the Contact, then deactivate the owner. So, am I right in thinking I should create a class with both activating and deactivating methods, then call the first method before the contact update and the second method after the contact update?

 

I.e. Put the method calls either side of:

  List<Contact> relatedContacts =
      [SELECT f.id, f.Accountid,  f.Owner.Name, f.Department__r.id, f.Ownerid, f.Owner.IsActive
      FROM Contact f
      WHERE f.Accountid IN :accountIds
      FOR UPDATE];
      
  for ( Contact child : relatedContacts ) {
        if ( accountMap.containsKey( child.Accountid ) ) {
        parent = accountMap.get( child.Accountid );
        child.Department__c = child.Accountid;
        child.Accountid = parent.Parent_Company__c;
        }
      }

Any tips/suggestions?

Shashikant SharmaShashikant Sharma

Your code should be like this

 

trigger UpdateChildObjectsToDepartment on Account (after update) {

  Map<Id, Account> accountMap = new Map<Id,Account>();
  Account parent;

//For changing company TO Deparment
  for (Integer i = 0; i < Trigger.new.size(); i++){
    if (Trigger.new[i].Department__c == true && Trigger.old[i].Department__c == false) {
      accountMap.put( Trigger.new[i].Id, Trigger.new[i]);
    }
  }
  
 
  if( accountMap.size() > 0 ) {
  Set<Id> accountIds = accountMap.keySet();
  
  List<Contact> relatedContacts =
      [SELECT f.id, f.Accountid,  f.Owner.Name, f.Department__r.id, f.Ownerid, f.Owner.IsActive
      FROM Contact f
      WHERE f.Accountid IN :accountIds
      FOR UPDATE];
  Set<ID> idsOfOwnerSet = new Set<ID>();
  for ( Contact child : relatedContacts ) {
        if ( accountMap.containsKey( child.Accountid ) ) {
        parent = accountMap.get( child.Accountid );
        child.Department__c = child.Accountid;
        child.Accountid = parent.Parent_Company__c;
        idsOfOwnerSet.add(child.f.Ownerid);
        }
      }
      
      
update relatedContacts;

updateUserClass.updateUserMethod(idsOfOwnerSet);
}}

 

 

 

 

Future Method Class

 

public class updateUserClass {

  @future 
  public static void updateUserMethod(Set<ID> setUsersToUpdate) 
  {
    List<User> usersToUpdate = [Select id from user where id in : setUsersToUpdate];//Put your logic to update
  }
}

 You can pass all the value that you want to update as premitive data types.

 

Let me know if any issues

brianspatsobrianspatso

Thanks again,

 

However, now, instead of getting the Mixed DML error, I am getting the following:

Error:Apex trigger UpdateChildObjectsToDepartment caused an unexpected exception, contact your administrator: UpdateChildObjectsToDepartment: execution of AfterUpdate caused by: System.DmlException: Update failed. First exception on row 0 with id 003R000000XvquZIAR; first error: CANNOT_REPARENT_RECORD, owner is inactive, cannot reparent record: []: Trigger.UpdateChildObjectsToDepartment: line 35, column 1

 Which I think means that because the Owner is being activated in the future, the Contact cannot be updated.

 

Can we put the Contact update in the future instead?

Shashikant SharmaShashikant Sharma

Just remove FOR UPDATE from Contact SOQL and try againg, if it does not work

 

please give me complete detail of your req

 

1) You are getting contacts for all account which are set departement flag from false to true

2)You are updated contact then, can you tell me in top of the trigger your are checking department__c as boolean but in update code you are assigning ID field in it.

3)Then waht field you want to update in User Object.

brianspatsobrianspatso

I'm afraid that didn't work.

 

To answer your questions:

1) Correct

2) There is a boolean field on the Account which determines if it is a Departments or not. The Contact.Department__c field is a lookup to Account, with a filter that requires Account.Department__c = TRUE. Hope that makes sense?

3) I need to activate the User before updating the Contact, else the update to the Contact will not work (i.e. the Contact cannot be updated when the Owner is inactive).

 

Hope that helps - let me know what you think.

 

Thanks again.

Shashikant SharmaShashikant Sharma

try this now

trigger UpdateChildObjectsToDepartment on Account (after update) {

  Map<Id, Account> accountMap = new Map<Id,Account>();
  Account parent;

//For changing company TO Deparment
  for (Integer i = 0; i < Trigger.new.size(); i++){
    if (Trigger.new[i].Department__c == true && Trigger.old[i].Department__c == false) {
      accountMap.put( Trigger.new[i].Id, Trigger.new[i]);
    }
  }
  
 
  if( accountMap.size() > 0 ) {
  Set<Id> accountIds = accountMap.keySet();
  
  List<Contact> relatedContacts =
      [SELECT f.id, f.Accountid,  f.Owner.Name, f.Department__r.id, f.Ownerid, f.Owner.IsActive
      FROM Contact f
      WHERE f.Accountid IN :accountIds
      FOR UPDATE];
  Set<ID> idsOfOwnerSet = new Set<ID>();
  
  Map<ID , ID> mapDepartemntId = new Map<ID, ID>();
  Map<ID , ID> mapAccountId = new Map<ID, ID>();
  Set<ID> setContactIds = new Set<ID>();
  for ( Contact child : relatedContacts ) {
        if ( accountMap.containsKey( child.Accountid ) ) {
        parent = accountMap.get( child.Accountid );
        mapDepartemntId.pur(child.id ,child.Accountid);
        //child.Department__c = child.Accountid;
        mapAccountId.pur(child.id ,parent.Parent_Company__c);        
        //child.Accountid = parent.Parent_Company__c;
        idsOfOwnerSet.add(child.f.Ownerid);
        setContactIds.add(child.id);
        }
      }
List<User> usersToUpdate = [Select id from user where id in : setUsersToUpdate];
for(User u : usersToUpdate)
{
u.IsActive =  true;
}
update;
updateContactClass.updateContacMethod(setContactIds , mapDepartemntId , mapAccountId );      



}}

 

Future Method Class

 

public class updateContactClass {

  @future 
  public static void updateContacMethod(Set<ID> setContact , Map<ID , ID> mapDepartemntId ,  Map<ID , ID> mapAccountId) 
  {
    List<Contact> contactList = [Select id from Contact where id in : setContact];
for(Contact c : contactList)
{
   c.Department__c = mapDepartemntId.get(c.id);
   c.Accountid = mapAccountId.get(c.id);

}
update contactList;
  }
}

 

 

I have updated the future class name and method name

brianspatsobrianspatso

I felt like this was going to work, but no joy. Maybe there is no way to do this?

 

This is the main code I used (yours but with a few tweaks):

 

trigger UpdateChildObjectsToDepartment on Account (after update) {

  Map<Id, Account> accountMap = new Map<Id,Account>();
  Account parent;

//For changing company TO Deparment
  for (Integer i = 0; i < Trigger.new.size(); i++){
    if (Trigger.new[i].Department__c == true && Trigger.old[i].Department__c == false) {
      accountMap.put( Trigger.new[i].Id, Trigger.new[i]);
    }
  }
  
 
  if( accountMap.size() > 0 ) {
  Set<Id> accountIds = accountMap.keySet();
  
  List<Contact> relatedContacts =
      [SELECT f.id, f.Accountid,  f.Owner.Name, f.Department__r.id, f.Ownerid, f.Owner.IsActive
      FROM Contact f
      WHERE f.Accountid IN :accountIds
      FOR UPDATE];

  Set<ID> idsOfOwnerSet = new Set<ID>();
  
  Map<ID , ID> mapDepartemntId = new Map<ID, ID>();
  Map<ID , ID> mapAccountId = new Map<ID, ID>();
  Set<ID> setContactIds = new Set<ID>();  

  for ( Contact child : relatedContacts ) {
        if ( accountMap.containsKey( child.Accountid ) ) {
        parent = accountMap.get( child.Accountid );
        mapDepartemntId.put(child.id ,child.Accountid);
        //child.Department__c = child.Accountid;
        mapAccountId.put(child.id ,parent.Parent_Company__c);        
        //child.Accountid = parent.Parent_Company__c;
        idsOfOwnerSet.add(child.Ownerid);
        setContactIds.add(child.id);
        }
      }
      
List<User> usersToUpdate = [Select id from user where id in : idsOfOwnerSet];
for(User u : usersToUpdate)
{
u.IsActive =  true;
}
update usersToUpdate;
updateContactClass.updateContacMethod(setContactIds , mapDepartemntId , mapAccountId ); 
   
}}

 

Unbeliveably, it still gave me the Mixed DML operation, and I can't work out why - since the Contact is not updated in this trigger, or even at the same time, I have no idea how it could be a problem.

 

Any ideas?

 

SteveBowerSteveBower

Hi, It's giving you this error because you're in an After Update trigger on Account, e.g. in this transaction an Account has already been updated and now you are trying to update a User.   (Go to the Help & Training popup in the UI and type in Mixed_DML_Operation and it will give you the doc on it.)  

 

Either way, I think you are going down a wrong path.  I think you should reparent the Contacts to active users.

 

 

But first, just fyi, I cleaned up your first attempt (which was pretty good).

 

trigger UpdateChildObjectsToDepartment on Account (after update) {
  // We are changing Accounts from Companies to Departments.  
  // We want to change all Contacts who are "in" those Department Accounts such that their  
  // 1. new Department Id now points to the Account we are changing, and 
  // 2. their new Account Id points to the Parent of the Account we are changing.

  Map<Id, Account> accountMap = new Map<Id,Account>();

  // Gather all the Accounts we're converting to Departments
  for (Account a : trigger.new)
     if (a.Department__c && !trigger.oldmap.get(a.id).Department__c) accountMap.put(a.id, a);
  
  if (accountMap.isEmpty()) return;
      
  // For each Contact in those Accounts....
  // There may be many children, process them in bulk...
  for (Contact[] children : [
      SELECT f.id, f.Accountid,  f.Owner.Name, f.Department__r.id, f.Ownerid, f.Owner.IsActive
      FROM Contact f
      WHERE f.Accountid IN :accountMap.keySet()]) {
        for (Contact child: children) {
            child.Department__c = child.Accountid;
            child.Accountid = accountMap.get(child.Accountid).Parent_Company__c;
        }
      update children;
  }
}

 

Now, this will fail for the cases where the Contact Owner is inactive, I think the explanation for what you see is:

 

-----------------------------------------------

Update Unsuccessful  The record owner is inactive.  Before you can make this change, assign this record to an active user.

 

This message will occur whenever two related records are owned by different users, where the child record owner is inactive, and the user tries to change the parent record.  As an example, Account A is owned by Active User 1 and Contact B is owned by Inactive User 2.  If you try to change the parent Account on Contact B, you will receive an error.

 

This is because when the owner of a child record is different from the parent, we create an implicit share on the parent record to provide access.  When we deactivate the user record, we retain the existing shares on the record, including the deactivated user (even if they can’t login any longer). However, when you try to transfer the child record to another parent, we try to add an implicit share to the new parent, which we can’t do since the user is now inactive and no new shares may be created for the inactive user (only existing shares are retained). As a result, you will be prevented from reparenting the child record until you change the record ownership to an active user or you reactivate the user.

-------------------------------------------------

 

 So, the path you're on is to split the children Contacts into two groups, those with Active owners which you can just update in the trigger, and those with inactive owners.  And, for the inactive ones you want to 

1. Reactivate the User,

2. Do the updates for that users Contact(s)

3. Deactivate the User.

 

It would be sweet if you could just select all the users, set them all to active, and then update them all at once, do all the Contact updates, and then deactive all the users.  And, if you only have a few users you might be able to get away with that.   However, if you have a lot of Users impacted, then you could use a bunch of your Salesforce licenses all at once and that becomes a different problem.  So, you probably want to do that one at a time?  And it has to be in an @Future method, because you can't mix the DML, etc. within one trigger.  This turns into a real pain.

 

 

So, the thought that comes to mind and makes me think you're going down a bad path is: why are you going to allow Contacts to be owned by Inactive Users if you are bothering to update them?  Why not re-parent them to the owner of the Account?  Isn't it just sort of questionable data modelling to update data with no sort of ownership of the data?    OR  

Just decide that the Contacts with Inactive Owners won't get updated.

 

I mean you're left with some ugly options...   If you really want to push all the user, contact, and contact update information into an @future class, cycle through the users one by one, and do the updates, you probably could.  But I don't think it's a good solution.

 

Is there any better path from a business analysis perspective to model what you need?

 

Best, Steve.

brianspatsobrianspatso

Hi Steve,

 

Thanks a lot for your response - really gave me a lot to consider and some explanations that I had not seen. And thanks for the cleaned code - it's good to see what I've done compared to the simpler version (helps me learn).

 

You're right, of course, that activating and deactivating a list of Owners is not the best way to go about it. The problem I am trying to solve is actually on Opportunities - but I have been using Contacts to test as they are easier to create and manipulate.

 

The problem stems from the fact that, on the one hand, we cannot permanently change the Owner of an Opportunity, because of everything that is tied into this value (such as commission). But on the other hand, the whole point of this Department structure is that all Deals for all Departments are listed against the Parent Companies.

 

So I cannot change the Deal Owner permanently, and yet I definitely need to update the Parent Company and Department values of the Opportunity.

 

What is most frustrating is that this is all because of the limitations of Salesforce! (But then I suppose all Apex code is written for that reason!!)

SteveBowerSteveBower

I think you could consider establishing two lookups to Users.   One being the default Owner relationship that Salesforce manages for you and that is giving you all this angst.   The other being a "Commission_Owner" field, or something along those lines that models your business needs for a persistent owner which is not the record owner.

 

In my opinion, if you're still mucking with the data, then the Account and Opportunity are still Owned by *someone*.  :-)

 

Best, Steve.

 

 

brianspatsobrianspatso

It looks like that might be my best solution at this point. It's just that there's a lot I would need to change for that, and I can't afford to get it wrong.

 

Unless you think calling Salesforce would help at all?

SteveBowerSteveBower

Hi, yeah, I understand.  This type of changes seeps into all sorts of places, reports, workflows, etc.  So I can understand the desire to avoid changing the lookups.

 

Perhaps there is some way it could be done by adding some sort of temporary Sharing rules?  I don't know.   Asking Salesforce is always a fine idea, but you might find that the level of response that you get isn't very sophisticated.

 

There's another guy on the board here named Bob Buzzard who I think is very good and perhaps he might have some ideas.

 

Best, Steve.

brianspatsobrianspatso

I've found a solution to this, which is difficult to explain, but I'll try.

 

My original idea was to have two fields on the Deal: a Parent Company and a Department. When the companies were updated or changed, the code I wrote was trying to swap them around in the child records.

 

However, the big problem with this was that I couldn't re-parent a child record when the owner was inactive.

 

My new idea changes it slightly. There is now only one field on the Deal: a Company field and this field can be either a Parent Company or a Department. There is also a second, hidden field which is called Parent Company. If the Company is a parent, then the Company name gets copied into Parent Company. If the Company is a department, then its parent will get copied into the Parent Company.

 

The advantage of this setup is that when changes are made, the trigger only needs to update the Parent Company field on the Deal. And since this field is a custom lookup, we can update it, even though the owner is inactive.

 

I believe this will work, although feel free to tell me otherwise!

This was selected as the best answer
SteveBowerSteveBower

Hi, Sounds promising... you're still using two fields, so I don't see any issues w/ updating the custom field.  I hope it works well.  :-)   Best, Steve.