0

I have an SObject variable in which I'm adding the field api names and the values respectively as it acts as a Map and later adding it to a List<SObject> variable. The below code works as expected.

String sObjectType = 'Account';
List<SObject> myList = new List<SObject>();
Schema.SObjectType currentObjectType = Schema.getGlobalDescribe().get(sObjectType);
SObject newRecordToBeCreated = currentObjectType.newSObject();
newRecordToBeCreated.put('RecordTypeId', currentObjectType.getDescribe().getRecordTypeInfosByName().get('AccRecType').getRecordTypeId());
myList.add(newRecordToBeCreated);
insert myList;

My apex logic will

  1. Try to find all the necessary fields from the Schema.getGlobalDescribe() class of x object
  2. Populates test data based on the field's Schema.DisplayType using the SObject variable (like shown above)
  3. Add's the SObject variable to the List<SObject> variable (also shown above)
  4. Finally return the List<SObject> for insertion from different class(es)

Issue:

The issue is with the Lookup or REFERENCE field. I know I first have to insert the Parent SObject, grab the Id and put it in the SObject variable. However, my whole logic is inside of a for loop (going through a list of fields).

Also my method will be used to create multiple records which will become a for loop inside of a for loop.

Is there a way to avoid the DML statement inside the inner for loop at least in this scenario?

(Not sure how to frame the question/title, so open to suggestions)

1
  • well, the fflib UnitOfWork Pattern abstracts all of this so you can can registerNew parent, child, granchild records without having to remember any ids in code; but in lieu of that, I'd suggest looking at open source test data generator libraries rather than reinventing this wheel
    – cropredy
    Dec 21, 2023 at 0:51

1 Answer 1

1

I believe that the heart of your dilemma is this:
How can we know, programmatically, that two records are supposed to be related to one another?

Using Salesforce-generated Ids is one answer to that question, the natural approach. As you've posited, it's an issue when you want to create parents and child records because you'd have DML inside of a loop.

The solution I've used in the past is to separate the creation of the in-memory SObject instances from the DML aspect. That requires finding another way to programmatically relate two records. Using an external id field could help (see this question of Adrian's from 2016), but I think a more general solution is to take advantage of the fact that we can use SObjects as the key for a Map.

In short:

  • Use a subset of the parent object's field to define a "key"
  • Put the parent records into a Map<SObject, SObject>, which you'll later use for DML (insert myParentMap.values();)
  • At the same time, prepare a Map<SObject, List<SObject>> which has the parent's "key" and stores a list of child records

You'll be able to insert your parent records and get your Ids (automatically set in the parent instances in myParentMap because of the "store by reference" nature of non-primitives in lists/maps).

Then you can simply loop over the parent keys, fetch the parent record (with Id), and do a nested loop over the child records to set their relationship field.

example (pseudocode):

// Helper method to handle generating the key so the code can be reused
// What the "key" contains is going to differ from situation to situation
// Basically, it needs to be like a database primary key, something that
//   can uniquely identify every record
public SObject generateKey(SObject target, List<String> keyFields) {
    SObject key = target.getSObjectType.newSObject();

    for(String field :keyFields) {
        key.put(field, target.get(field));
    }

    return key;
}

Map<SObject, SObject> parentsByParentKeyMap = new Map<SObject, SObject>();
Map<SObject, List<SObject>> childrenByParentKeyMap = new Map<SObject, List<SObject>>();

// Create the in-memory instances
for(SObjectType parentSObjType :someListOfParents) {
    SObject parent = parentSObjType.newSObject();

    for(String parentField :parentValuesByField.keySet()) {
        parent.put(parentField, parentValuesByField.get(parentField));
    }
    
    SObject parentKey = generateKey(parent, new List<String>{'Name', 'Region__c'});

    // One important thing to keep in mind is that once you use something
    //   as a key in a map, that object instance should _never_ be modified
    parentsByParentKeyMap.put(parentKey, parent);

    // Again, thanks to the store-by-reference nature, it doesn't matter if
    //   we create this list now and populate it later.
    // Separately defining the childList saves us the trouble of fetching
    //   it in the child loop
    List<SObject> childList = new List<SObject>();
    childrenByParentKeyMap.put(parentKey, childList);

    for(SObjectType childSObjType :someListOfChildren) {
        SObject child = childSObjType.newSObject();

        for(String childField :childValuesByField.keySet()) {
            child.put(childField, childValuesByField.get(childField));
        }

        childList.add(child);
    }
}

// Perform DML for the parents
insert parentsByParentKeyMap.values();

// Go back and set relationships for the children
// And gather them all in a single list so you can do a single insert
List<SObject> flatChildrenList = new List<SObject>();
for(SObject key :parentsByParentKeyMap.keySet()) {
    SObject parent = parentsByParentKeyMap.get(key);

    for(SObject child :childrenByParentKeyMap.get(key)) {
        child.put('Parent__c', parent.get('Id'));

        flatChildrenList.add(child);
    }
}

// Finally, DML the children
insert flatChildrenList;

This does assume that you'll be creating the parent instances first

6
  • N.B. this is more or less what the fflib UnitOfWork does
    – cropredy
    Dec 21, 2023 at 2:12
  • It would be good, since you mention using an SObject as a map key, to say that the SObject state must not change once used as a key since such changes prevent the get from functioning correctly (typically causing a map lookup failure unless you System.debug out the map first, since this happens to recalculate the bucket mapping).
    – Phil W
    Dec 21, 2023 at 8:08
  • (BTW, and this isn't important for the Q&A but rather I am curious; any reason you do not have a space after the for loop's : iterator operator? It makes this seem to be the same as a SOQL binding variable, which it isn't...)
    – Phil W
    Dec 21, 2023 at 8:12
  • @PhilW I did, in fact, mention that the SObject key must not be modified (in a comment in my example code, at an appropriate location). The for loop thing is just code style (shrug).
    – Derek F
    Dec 21, 2023 at 12:31
  • Ah, I did not focus on the comments in the pseudocode. It's one of those points I would have covered in a Note, but to each their own.
    – Phil W
    Dec 21, 2023 at 13:39

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .