@@ -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