You can dynamically load multiple records to parent and child objects simultaneously in a single DML call, using an External ID field on the parent object. By delving into this article, you will gain the following:
- A thorough understanding of what the External ID field is and how it operates
- A dive into how leveraging External IDs may streamline data integration process
Let’s get started…
Understanding External IDs
An External ID is a standard or custom field in Salesforce that generally holds identifiers coming from external systems such as ERPs or third-party applications.
An External ID supports the following field types:
- Text
- Number
- Auto-Number
- Text Encrypted
Leveraging External IDs to Streamline Data Integration
Basically, associating a child record to its parent using an External ID field is an alternative to using the parent record ID field and necessitates the following:
- Having a parent object with a custom field defined as External ID
- Creating one or multiple parent records while specifying the External ID relative values
- Setting a parent record as a nested sObject on the corresponding child records to create
Let’s look at the following practical examples to address a scenario involving the insertion of multiple accounts and contact records in either of two ways: basic and advanced.
The Basic Way
This method involves creating the accounts and then creating the contact records while specifying their parent accounts as nested records.
Refer to this code:
public class CreateParentChild {
/**
*Inserting Parent And Child records using multiple DML statements.
*/
public static void basicWay() {
//Inserting Accounts
List<Account> accountList = new List<Account>();
for (Integer i=0; i<5; i++) {
accountList.add( new Account(Name = 'Test Account_' + i
,External_ID__c = 'Test Account External_' + i
)
);
}
insert accountList;
//Inserting Contacts
List<Contact> contactList = new List<Contact>();
for (Account acc: accountList) {
Contact con = new Contact( FirstName = 'First_Name'
,LastName = 'Last_Name'
,Account = acc
);
contactList.add(con);
}
insert contactList;
//USAGE
System.debug('Number of DML statements used so far: ' + Limits.getDmlStatements());
}
/**
*Inserting Parent And Child records dynamically using a single DML statement.
*/
public static void advancedWay() {
final String parentObjAPIName = 'Account';
final String childObjAPIName = 'Contact';
//Extracting RelationShip properties between child and parent objects
String childToParentRelationShipFieldName = null;
for (Schema.ChildRelationship cr: Schema.getGlobalDescribe()
.get(parentObjAPIName)
.getDescribe()
.getChildRelationships()
)
{
if ( String.valueOf(cr.getChildSObject()) == childObjAPIName ) {
childToParentRelationShipFieldName =String.valueOf(cr.getField());
}
}
//Getting the Relationship Name Between Parent and Child
String childToParentRelationShipName = Schema.getGlobalDescribe()
.get(childObjAPIName)
.getDescribe()
.fields
.getMap()
.get(childToParentRelationShipFieldName)
.getDescribe()
.getRelationshipName();
List<SObject> parentList = new List<SObject>();
List<SObject> childList = new List<SObject>();
List<SObject> resultList = new List<SObject>();
for (Integer i=0; i<5; i++) {
String external_ID = 'Test Account External_' + i;
SObject parentObj = Schema.getGlobalDescribe().get(parentObjAPIName).newSObject();
parentObj.put('Name' , ('Test Account_' + i) );
parentObj.put('External_ID__c' , external_ID );
parentList.add(parentObj);
//Create a clone of the Parent Object for foreign key reference
SObject parObjClone = Schema.getGlobalDescribe().get(parentObjAPIName).newSObject();
parObjClone.put('External_ID__c' , external_ID );
SObject childObj = Schema.getGlobalDescribe().get(childObjAPIName).newSObject();
childObj.put('FirstName' , ('First_Name_'+i) );
childObj.put('LastName' , ('Last_Name_'+i) );
childObj.putSObject(childToParentRelationShipName,parObjClone);
childList.add(childObj);
}
resultList.addAll(parentList);
resultList.addAll(childList);
try
{
insert resultList;
} catch(Exception e) {
System.debug('Exception=' + e);
}
//USAGE
System.debug('Number of DML statements used so far: ' + Limits.getDmlStatements());
}
}
Executing this method will consume two DML statements in terms of governor limits.
The Advanced Way
This method involves the following:
- Specifying the names of the parent and child objects.
- Extracting the properties of the relationship between child and parent objects.
- Creating parent accounts.
- Creating clones of parent accounts for foreign key references. This cloned object serves to overcome a
System.DMLException
. - Creating child records while adding the cloned parent object as a nested sObject throughout the relationship field.
Refer to this code:
public class CreateParentChild {
/**
*Inserting Parent And Child records using multiple DML statements.
*/
public static void basicWay() {
//Inserting Accounts
List<Account> accountList = new List<Account>();
for (Integer i=0; i<5; i++) {
accountList.add( new Account(Name = 'Test Account_' + i
,External_ID__c = 'Test Account External_' + i
)
);
}
insert accountList;
//Inserting Contacts
List<Contact> contactList = new List<Contact>();
for (Account acc: accountList) {
Contact con = new Contact( FirstName = 'First_Name'
,LastName = 'Last_Name'
,Account = acc
);
contactList.add(con);
}
insert contactList;
//USAGE
System.debug('Number of DML statements used so far: ' + Limits.getDmlStatements());
}
/**
*Inserting Parent And Child records dynamically using a single DML statement.
*/
public static void advancedWay() {
final String parentObjAPIName = 'Account';
final String childObjAPIName = 'Contact';
//Extracting RelationShip properties between child and parent objects
String childToParentRelationShipFieldName = null;
for (Schema.ChildRelationship cr: Schema.getGlobalDescribe()
.get(parentObjAPIName)
.getDescribe()
.getChildRelationships()
)
{
if ( String.valueOf(cr.getChildSObject()) == childObjAPIName ) {
childToParentRelationShipFieldName =String.valueOf(cr.getField());
}
}
//Getting the Relationship Name Between Parent and Child
String childToParentRelationShipName = Schema.getGlobalDescribe()
.get(childObjAPIName)
.getDescribe()
.fields
.getMap()
.get(childToParentRelationShipFieldName)
.getDescribe()
.getRelationshipName();
List<SObject> parentList = new List<SObject>();
List<SObject> childList = new List<SObject>();
List<SObject> resultList = new List<SObject>();
for (Integer i=0; i<5; i++) {
String external_ID = 'Test Account External_' + i;
SObject parentObj = Schema.getGlobalDescribe().get(parentObjAPIName).newSObject();
parentObj.put('Name' , ('Test Account_' + i) );
parentObj.put('External_ID__c' , external_ID );
parentList.add(parentObj);
//Create a clone of the Parent Object for foreign key reference
SObject parObjClone = Schema.getGlobalDescribe().get(parentObjAPIName).newSObject();
parObjClone.put('External_ID__c' , external_ID );
SObject childObj = Schema.getGlobalDescribe().get(childObjAPIName).newSObject();
childObj.put('FirstName' , ('First_Name_'+i) );
childObj.put('LastName' , ('Last_Name_'+i) );
childObj.putSObject(childToParentRelationShipName,parObjClone);
childList.add(childObj);
}
resultList.addAll(parentList);
resultList.addAll(childList);
try
{
insert resultList;
} catch(Exception e) {
System.debug('Exception=' + e);
}
//USAGE
System.debug('Number of DML statements used so far: ' + Limits.getDmlStatements());
}
}
Executing this method will consume only one DML statement in terms of governor limits.
Note: This method works also when performing updates or upserts.
Hope this advanced method will help you optimize your data ingestion jobs, especially when you have a scarcity in terms of resources.
Summary
This example demonstrates a different approach for handling the creation of multiple parent and child records in a single call, using a custom External ID field defined on the parent object and efficiently managing the available resources.
Any thoughts? Leave them in the comments below!