Know what’s happening if a Parent object is deleted?

Blog | Technology

As you know, Salesforce uses various types of automation for relieving users of the need for manual operations, so a typical organization has a huge number of Processes, Workflow Rules, etc. If declarative automation cannot meet the business objectives, developers may resort to writing Apex triggers.

How we encountered the problem?

Some time ago, we were asked to implement certain business rules for detail records via Process Builder that would fire after the related master record was deleted. After implementing the requirements, we started observing unexpected system behavior, where none of the rules were working after the deletion of the master record. After searching the web for information on the issue, we found an article, which explained the behavior. In particular, if there is an after (or before) delete (or undelete) trigger on the detail object, it will not fire if the master record is deleted and the detail records are subsequently cascade deleted. According to the order of execution, a trigger always fires before a process, but since the trigger did not fire on the change, no process can be fired.

Trying to reproduce the issue

We thought that the same could be true for a lookup relationship, so we decided to test the behavior. Before doing so, we did a thorough search on the Net. No sources, not even the official documentation, provided any definitive answers. We decided to conduct an experiment.

We created the data model illustrated below.

Screenshot of Schema Builder: Parent and Child relationship objects in Salesforce

We also created this process in Process Builder.

Screenshot of Process Builder: Update City on Child Object; 1-st condition

Screenshot of Process Builder: Update City on Child Object; 2-nd condition

During the first update, when the ParentID lookup field is not null, the City value is simply copied from the parent record to the Parent_City field on the child record. Where the ParentID lookup field is null, we set the Parent_City to “No city”.

If we delete the parent record from the child record detail page, the trigger will detect the change, and the automation will process the parent record delete operation. However, if the user opens the parent record’s detail page and deletes the child record there, the trigger on the child object will not detect the change in the lookup field, and the process will not fire the appropriate rules. What if some other Apex code deletes the parent record once certain conditions are met? Apparently, the information will be corrupted, which will lead to invalid reports or statistics, a critical issue for business. It is therefore important not to allow this type of potential issues to appear in your organizations.

Testing by writing a trigger

To ensure that child records are properly updated and prevent the situation described above, you should create a before delete trigger on the parent object and its handler class, as well as a before delete trigger on the child object and its handler class. Then you should write a service class that accepts the collection of child IDs to be processed and performs all the required work asynchronously. The service class is needed for avoiding duplication of the business rules and separating it from unrelated tasks.

A suggested parent trigger, handler class and service class can be found below. This example uses synchronous processing of child records for ease of understanding. One transaction can handle up to 150,000 records, i.e. the limit on one data manipulation language (DML) operation is 10,000 records, and the limit of the number of DML operations is 150. A versatile solution should use batch for asynchronous processing of child records.

trigger ParentTrigger on Parent__c (
	before insert, before update, before delete,
	after insert, after update, after delete
	){
	
	if(Trigger.isDelete && Trigger.isBefore){
		ParentHandler.findChildrenIds(Trigger.old);
	}
}

ParentHandler contains a method that searches for dependent child records. A method that can be used in ChildHandler independently from ParentHandler for setting “No city” for the other set of child records has been moved to the service class.

public class ParentHandler {
public static void findchildrenIDS(List<Parent__c> records) {
List<Id> = new List<Id>();

for (Parent_c parent : records){
parentIds.add(parent.Id);
}
Integer amountOfchildren = [SELECT COUNT() FROM Child WHERE ParentId__c IN :parentIds];
if (amountOfChildren  >= 150000){
Integer offset = 0;
Integer circles = Math.round(amountofChildren/10000);

for (Integer i=0; i < circles; i++) {
List<Child__c> children = [SELECT Id FROM Child__c WHERE ParentId c =: LIMIT 10000 OFFSET :offset];
Service.updateChildren(children);
offset += 10000;
}

Integer difference amountofchildren - offset;

if (difference < 10000 & difference != 0){
List<Child_c> children [SELECT id FROM Child_C WHERE ParentId_c =: parentIds LIMIT 10000 OFFSET :offset];
Service. updateChildren(children);
}
}
}
}

 

public class Service {
public static void updateChildren(List<Child__c> children){
for(Child__c child : children){
child.Parent_City__c = 'No City';
}
update children;
}
}

Results

We have thus proved the existence of these poorly publicized issues, which are also true for lookup relationships. We have examined an example and proposed a remedy.

Contact us today! 

Diving deep is our motto. Drop us a line and we'll show you what you can achieve with Salesforce!