+ Start a Discussion
INW_BryanINW_Bryan 

Updating records on cousin custom object

We have an app that is made up of almost entirely custom objects. Creating a smoothe workflow for our users is vital for this app. I'm new to apex, but familiar with basic code writting.

 

Here are the relationships between the objects: (--< = 1 to many, M/D = Master/Detail)

 

Object_a --< Object_b --M/D--< Object_c

Object_a --M/D--< Object_d

 

When a user populates a date field on Object_c and saves, we'd like a date field on all related records on Object_d (i.e. all Object_d's records for that specific Object_a record) to be updated with the same value. 

 

Any help is appreciated.

Best Answer chosen by Admin (Salesforce Developers) 
JayNicJayNic

So if I'm reading your schema right:

 

D looks up to A with a Master Detail

C looks up to B with a master detail

B loops up to A with a lookup

 

So to write a trigger to do this AND make sure it's batchable - because you could easily hit limits doing this I would do the following:

1) Create a formula field on object C that references object A's id (example: ObjectAId__c).

The formula would be something like: "CASESAFEID(ObjectB__r.ObjectA__c)"

This will minimize soql queries and make maintenace easier

2) create a batchable trigger as follows:

 

trigger ObjectC_aU on ObjectC__c (after update){
/*
Processes:
	Set the dates on cousin D objects based on our date
*/
	map<id,date> objectADates = map<id,date>(); //Create a map of Object A ids to object c dates
	for(objectC__c c : trigger.new) {
		ObjectC__c old = trigger.oldMap.get(c.id); //Reference the previous values from the update
		if(old.DateField__c != c.DateField__c) { //If we are changed - then we have an update to do!
			if(objectADates.get(c.ObjectAId__c) == null) {
				objectADates.put(c.ObjectAId__c,c.DateField__c);
			} else {
				//What happens if there are sibling c records that are updating the object with a different date? This if your own business logc that can be added here
				// It may not be needed - just a thought
			}
		}
	}
	
	//Check if we have work to do before moving on... otherwise don't bother using up a soql query
	if(!objectADates.isEmpty()) {
		list<ObjectD__c> cousins = [SELECT id, ObjectA__c FROM ObjectD__c WHERE ObjectA__c IN :objectADates.keySet()]; //Get all the cousing d objects based on the keySet of the map we created earlier
		if(!cousins.isEmpty()) {
			for(ObjectD__c d : cousins) {
				d.DateField__c = objectADates.get(d.ObjectA__c); //Grab the date based on the map
			}
			update cousins;
		}
	}
}

 

That should do it

 

PS: On a side note: when you are using multiple MD relationships and are updating cross relationship like this, you will notice that updates will be called multiple times. This is just how MD relationships work (especially when there are roll up fields involved) so it is EXTREMELY important to be very EXPLICIT about your conditions where the updates on the related object occurr - otherwise you will hit stack depth exceptions

All Answers

JayNicJayNic

So if I'm reading your schema right:

 

D looks up to A with a Master Detail

C looks up to B with a master detail

B loops up to A with a lookup

 

So to write a trigger to do this AND make sure it's batchable - because you could easily hit limits doing this I would do the following:

1) Create a formula field on object C that references object A's id (example: ObjectAId__c).

The formula would be something like: "CASESAFEID(ObjectB__r.ObjectA__c)"

This will minimize soql queries and make maintenace easier

2) create a batchable trigger as follows:

 

trigger ObjectC_aU on ObjectC__c (after update){
/*
Processes:
	Set the dates on cousin D objects based on our date
*/
	map<id,date> objectADates = map<id,date>(); //Create a map of Object A ids to object c dates
	for(objectC__c c : trigger.new) {
		ObjectC__c old = trigger.oldMap.get(c.id); //Reference the previous values from the update
		if(old.DateField__c != c.DateField__c) { //If we are changed - then we have an update to do!
			if(objectADates.get(c.ObjectAId__c) == null) {
				objectADates.put(c.ObjectAId__c,c.DateField__c);
			} else {
				//What happens if there are sibling c records that are updating the object with a different date? This if your own business logc that can be added here
				// It may not be needed - just a thought
			}
		}
	}
	
	//Check if we have work to do before moving on... otherwise don't bother using up a soql query
	if(!objectADates.isEmpty()) {
		list<ObjectD__c> cousins = [SELECT id, ObjectA__c FROM ObjectD__c WHERE ObjectA__c IN :objectADates.keySet()]; //Get all the cousing d objects based on the keySet of the map we created earlier
		if(!cousins.isEmpty()) {
			for(ObjectD__c d : cousins) {
				d.DateField__c = objectADates.get(d.ObjectA__c); //Grab the date based on the map
			}
			update cousins;
		}
	}
}

 

That should do it

 

PS: On a side note: when you are using multiple MD relationships and are updating cross relationship like this, you will notice that updates will be called multiple times. This is just how MD relationships work (especially when there are roll up fields involved) so it is EXTREMELY important to be very EXPLICIT about your conditions where the updates on the related object occurr - otherwise you will hit stack depth exceptions

This was selected as the best answer
INW_BryanINW_Bryan
Thanks for the quick response, JayNic. I'll give it a whirl and mark as solution if everything goes well.
INW_BryanINW_Bryan

I was able to get this to work. Your solution works perfectly given how I described the issue. 

 

Unfortunately for me, I need this to work with both the After Insert & After Update. trigger.oldMap does not work with After Insert. Is there a way around this?

 

Thanks again,

-Bryan

JayNicJayNic

You can do a check against trigger.isInsert

 

trigger ObjectC_aU on ObjectC__c (after update){
/*
Processes:
	Set the dates on cousin D objects based on our date
*/
	map<id,date> objectADates = map<id,date>(); //Create a map of Object A ids to object c dates
	for(objectC__c c : trigger.new) {
		if(trigger.isInsert || (trigger.oldMap.get(c.id).DateField__c != c.DateField__c)) { //If we are changed - then we have an update to do!
			if(objectADates.get(c.ObjectAId__c) == null) {
				objectADates.put(c.ObjectAId__c,c.DateField__c);
			} else {
				//What happens if there are sibling c records that are updating the object with a different date? This if your own business logc that can be added here
				// It may not be needed - just a thought
			}
		}
	}
	
	//Check if we have work to do before moving on... otherwise don't bother using up a soql query
	if(!objectADates.isEmpty()) {
		list<ObjectD__c> cousins = [SELECT id, ObjectA__c FROM ObjectD__c WHERE ObjectA__c IN :objectADates.keySet()]; //Get all the cousing d objects based on the keySet of the map we created earlier
		if(!cousins.isEmpty()) {
			for(ObjectD__c d : cousins) {
				d.DateField__c = objectADates.get(d.ObjectA__c); //Grab the date based on the map
			}
			update cousins;
		}
	}
}

 

 

Try that, should work