Skip to content

Commit 075bb9f

Browse files
⭐ Add support for upsert by external Id
1 parent 46e2e79 commit 075bb9f

File tree

1 file changed

+91
-12
lines changed

1 file changed

+91
-12
lines changed

sfdx-source/apex-common/main/classes/fflib_SObjectUnitOfWork.cls

Lines changed: 91 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ public virtual class fflib_SObjectUnitOfWork
6060

6161
protected Map<String, Map<Id, SObject>> m_dirtyMapByType = new Map<String, Map<Id, SObject>>();
6262

63+
protected Map<String, List<SObject>> m_upsertRecordsPerType = new Map<String, List<SObject>>();
64+
protected Map<String, Schema.SObjectField> m_externalIdToUpsertPerType = new Map<String, Schema.SObjectField>();
65+
6366
protected Map<String, Map<Id, SObject>> m_deletedMapByType = new Map<String, Map<Id, SObject>>();
6467
protected Map<String, Map<Id, SObject>> m_emptyRecycleBinMapByType = new Map<String, Map<Id, SObject>>();
6568

@@ -88,6 +91,7 @@ public virtual class fflib_SObjectUnitOfWork
8891
{
8992
void dmlInsert(List<SObject> objList);
9093
void dmlUpdate(List<SObject> objList);
94+
void dmlUpsert(List<SObject> objList, Schema.SObjectField externalId);
9195
void dmlDelete(List<SObject> objList);
9296
void eventPublish(List<SObject> objList);
9397
void emptyRecycleBin(List<SObject> objList);
@@ -103,7 +107,12 @@ public virtual class fflib_SObjectUnitOfWork
103107
{
104108
update objList;
105109
}
106-
public virtual void dmlDelete(List<SObject> objList)
110+
public void dmlUpsert(List<SObject> objList, Schema.SObjectField externalId) {
111+
if (!objList.isEmpty()) {
112+
Database.upsert(objList, externalId);
113+
}
114+
}
115+
public void dmlDelete(List<SObject> objList)
107116
{
108117
delete objList;
109118
}
@@ -181,6 +190,7 @@ public virtual class fflib_SObjectUnitOfWork
181190
// add type to dml operation tracking
182191
m_newListByType.put(sObjectName, new List<SObject>());
183192
m_dirtyMapByType.put(sObjectName, new Map<Id, SObject>());
193+
m_upsertRecordsPerType.put(sObjectName, new List<SObject>());
184194
m_deletedMapByType.put(sObjectName, new Map<Id, SObject>());
185195
m_emptyRecycleBinMapByType.put(sObjectName, new Map<Id, SObject>());
186196
m_relationships.put(sObjectName, new Relationships());
@@ -456,6 +466,59 @@ public virtual class fflib_SObjectUnitOfWork
456466
}
457467
}
458468

469+
/**
470+
* Register a list of mix of new and existing records to be inserted updated during the commitWork method by and external id
471+
*
472+
* @param records A list of mix of new and existing records
473+
* @param externalIdField External id field to use for upserting operation
474+
**/
475+
public void registerUpsert(List<SObject> records, Schema.SObjectField externalIdField)
476+
{
477+
for (SObject record : records)
478+
{
479+
this.registerUpsert(record, externalIdField, null, null);
480+
}
481+
}
482+
483+
484+
/**
485+
* Register an existing or new record to be upserted when commitWork is called using an external id field,
486+
* you may also provide a reference to the parent record instance (should also be registered as new separately)
487+
*
488+
* @param record A newly created SObject instance to be inserted during commitWork
489+
* @param relatedToParentField A SObjectField reference to the child field that associates the child record with its parent
490+
* @param relatedToParentRecord A SObject instance of the parent record (should also be registered as new separately)
491+
**/
492+
public void registerUpsert(SObject record, Schema.SObjectField externalIdField, Schema.sObjectField relatedToParentField, SObject relatedToParentRecord)
493+
{
494+
495+
SObjectType sObjectType = record.getSObjectType();
496+
String sObjName = sObjectType.getDescribe().getName();
497+
498+
assertForNonEventSObjectType(sObjName);
499+
assertForSupportedSObjectType(m_upsertRecordsPerType, sObjName);
500+
501+
if (externalIdField == null)
502+
throw new UnitOfWorkException('Invalid argument: externalIdField. If you want to upsert by id, use the registerUpsert method that has only one argument');
503+
504+
assertValidExternalId(sObjectType, externalIdField);
505+
506+
Schema.SObjectField registeredExternalId = m_externalIdToUpsertPerType.get(sObjName);
507+
if (registeredExternalId != null && registeredExternalId != externalIdField)
508+
{
509+
throw new UnitOfWorkException(String.format(
510+
'SObject type {0} has already registered an upsert by external id {1}, you cannot use another is this unit of work.',
511+
new List<String> {sObjName, registeredExternalId.getDescribe().getName()}
512+
));
513+
}
514+
515+
m_upsertRecordsPerType.get(sObjName).add(record);
516+
m_externalIdToUpsertPerType.put(sObjName, externalIdField);
517+
518+
if (relatedToParentRecord!=null && relatedToParentField!=null)
519+
registerRelationship(record, relatedToParentField, relatedToParentRecord);
520+
}
521+
459522
/**
460523
* Register an existing record to be deleted during the commitWork method
461524
*
@@ -624,6 +687,7 @@ public virtual class fflib_SObjectUnitOfWork
624687
onDMLStarting();
625688
insertDmlByType();
626689
updateDmlByType();
690+
upsertDmlByType();
627691
deleteDmlByType();
628692
emptyRecycleBinByType();
629693
resolveEmailRelationships();
@@ -687,6 +751,15 @@ public virtual class fflib_SObjectUnitOfWork
687751
}
688752
}
689753

754+
private void upsertDmlByType()
755+
{
756+
String sobjName;
757+
for (Schema.SObjectType sObjectType : m_sObjectTypes) {
758+
sobjName = sObjectType.getDescribe().getName();
759+
m_dml.dmlUpsert(m_upsertRecordsPerType.get(sobjName), m_externalIdToUpsertPerType.get(sobjName));
760+
}
761+
}
762+
690763
private void deleteDmlByType()
691764
{
692765
Integer objectIdx = m_sObjectTypes.size() - 1;
@@ -775,6 +848,22 @@ public virtual class fflib_SObjectUnitOfWork
775848
}
776849
}
777850

851+
private static void assertValidExternalId(Schema.SObjectType relatedObject, Schema.SObjectField externalIdField)
852+
{
853+
854+
String externalIdFieldName = externalIdField.getDescribe().getName();
855+
Boolean relatedHasExternalIdField = relatedObject.getDescribe().fields.getMap().keySet().contains(externalIdFieldName.toLowerCase());
856+
Boolean externalIdFieldIsValid = externalIdField.getDescribe().isExternalId();
857+
858+
if (!relatedHasExternalIdField) {
859+
throw new UnitOfWorkException('Invalid argument: externalIdField. Field supplied is not a known field on the target sObject.');
860+
}
861+
862+
if (!externalIdFieldIsValid) {
863+
throw new UnitOfWorkException('Invalid argument: externalIdField. Field supplied is not a marked as an External Identifier.');
864+
}
865+
}
866+
778867
private class Relationships
779868
{
780869
private List<IRelationship> m_relationships = new List<IRelationship>();
@@ -804,17 +893,7 @@ public virtual class fflib_SObjectUnitOfWork
804893
List<Schema.SObjectType> relatedObjects = relatedToField.getDescribe().getReferenceTo();
805894
Schema.SObjectType relatedObject = relatedObjects[0];
806895

807-
String externalIdFieldName = externalIdField.getDescribe().getName();
808-
Boolean relatedHasExternalIdField = relatedObject.getDescribe().fields.getMap().keySet().contains(externalIdFieldName.toLowerCase());
809-
Boolean externalIdFieldIsValid = externalIdField.getDescribe().isExternalId();
810-
811-
if (!relatedHasExternalIdField) {
812-
throw new UnitOfWorkException('Invalid argument: externalIdField. Field supplied is not a known field on the target sObject.');
813-
}
814-
815-
if (!externalIdFieldIsValid) {
816-
throw new UnitOfWorkException('Invalid argument: externalIdField. Field supplied is not a marked as an External Identifier.');
817-
}
896+
assertValidExternalId(relatedObject, externalIdField);
818897

819898
RelationshipByExternalId relationship = new RelationshipByExternalId();
820899
relationship.Record = record;

0 commit comments

Comments
 (0)