0

Having the next code:

        for (WrapperTest pb : pbs_lst) {
                        
                        Pricebook2 newPB2 = new Pricebook2();
                        PricebookEntry newPBE = new PricebookEntry();
                        
                        newPB2.ExternalLink__c = pb.externalinkPricebook2;
                        newPB2.IsActive = pb.isactive;
                        newPB2.Name = 'TestName';
            
                        newPBE.ExternalLink__c = pb.externalinkEntry;
                        newPBE.UnitPrice = pb.unit_price;
        
                        Pricebook2 pb2EntryAux = new Pricebook2(ExternalLink__c = newPB2.ExternalLink__c);
                        newPBE.Pricebook2Id = pb2EntryAux.ID;
        
                        System.debug('PBE PRICEBOOK2 ID --< : '+  newPBE.Pricebook2Id);
        
                        
                    }

//PBE PRICEBOOK2 ID --< : null
    //Then upsert operations

I want to use this For loop to could update both objects avoiding doing 1 for loop to Pricebook2 and another one to PricebookEntry. But the problem is that, ath the point of the newPBE.Pricebook2Id assignment, Id of the Pricebook2 is null because is not commited yet.

So, there is any option to could assign the ID? Or is it possible to could update both objects using just 1 for loop + upsert operation?

2 Answers 2

2

If you have a field on Pricebook2 marked as an "external Id", and there are no duplicate values, then it is possible to accomplish this in a single loop and a single upsert1.

In general, the pattern would look something like this:

List<Account> accountsToUpsert = new List<Account>();
List<Account> children = new List<Account>();

for(<something>){
    // Just trying to convey that all the external ids need to be unique
    String externalId = 'myId' + i;
    Account parent = new Account(Name = 'Parent-X', My_External_Id__c = externalId);

    // This is what allows Salesforce to auto-relate the child record
    // It must be an instance of the SObject with only the external Id populated)
    Account child = new Account(Name = 'Child-X', Parent = new Account(My_External_Id__c = externalId));

    // The other part is that the corresponding parent record needs to be inserted before
    //   the child record.
    // Using two separate lists like this to achieve that isn't strictly necessary
    //   but it is easy/safe.
    accountsToUpsert.add(parent);
    children.add(child);
}

// The end result is that our list contains all of the parents first, then all of the
//   children.
accountsToUpsert.addAll(children);

upsert accountsToUpsert My_External_Id__c;

If that's not an option for you, then there's no way to avoid needing to perform two dml (outside of the composite api) and two loops. It is possible to do most of the work in the first loop though.

// The change here is that we have a map to help us keep track of which parents a
//   child record should be related to
// The key of the map being an SObject means we need to be careful
Map<SObject, List<SObject>> parentKeyToChildrenMap = new Map<SObject, List<SObject>>();

List<Account> parentsList = new List<Account>();
List<Account> childrenList = new List<Account>();

for(<something>){
    // Create the parent record like usual
    Account parent = new Account(Name = 'Parent-X', field1__c = 'value1', field2__c = 'value2');

    // The "key" account we use for the map should be something we can derive from
    //   the parent record alone.
    // Because it's a key, it needs to be unique to the transaction (but it's not an
    //   issue if there's already a stored record with the same value(s) like it is
    //   when using an external id)
    Account parentKey = new Account(Name = parent.Name);

    Account child = new Account(Name = 'Child-X');

    // The child records don't get added to the list in this loop
    parentsList.add(parent);

    // Standard map initialization
    if(!parentKeyToChildrenMap.containsKey(parentKey)){
        parentKeyToChildrenMap.put(parentKey, new List<Account>());
    }

    parentKeyToChildrenMap.get(parentKey).add(child);
}

// First upsert takes care of parents
upsert parentsList;

// Then we loop again to assign the parent ids that we now have
for(parent :parentsList){
    // This is why it's important to be able to derive the key from the parent (and
    //   only the parent).
    // We need to reconstruct it so we can get at the child records
    // We can't just use the parent record itself, since it'll now have an Id
    //   (and thus results in a different hash)
    Account key = new Account(Name = parent.Name);

    for(child :parentKeyToChildrenMap.get(key)){
        child.ParentId = parent.Id;
        childrenList.add(child);
    }
}

upsert chilrenList;

1: After some additional testing, this is possible for inserts, updates, and upserts. It's possible even if the parent and child records are of the same SObject. The catch here is that the parent and child records need to be in separate trigger "chunks" (triggered records are chunked into groups of 200, with the final chunk possibly containing fewer records. AllOrNone = false complicates that statement). This means that you cannot use a single DML statement for this if you have fewer than 200 parent records.

It's similar for different parent-child SObjects. The parent SObject needs to be DML'd before the child SObject. As long as your List<SObject> contains all of the parent records first, then you add all of the child records, it should turn out fine. You can have fewer than 200 parent records if this is the case.

0
0

I can think of 2 ways.

Check this link: where there is a sample code given to insert opportunity and account in the same transaction.

public class ParentChildSample {
    public static void InsertParentChild() {
        Date dt = Date.today();
        dt = dt.addDays(7);
        Opportunity newOpportunity = new Opportunity(
            Name='OpportunityWithAccountInsert',
            StageName='Prospecting',
            CloseDate=dt);
        
        // Create the parent reference.
        // Used only for foreign key reference
        // and doesn't contain any other fields.
        Account accountReference = new Account(
            MyExtID__c='SAP111111');                
        newOpportunity.Account = accountReference;
        
        // Create the Account object to insert.
        // Same as above but has Name field.
        // Used for the insert.
        Account parentAccount = new Account(
            Name='Hallie',
            MyExtID__c='SAP111111');      
        
        // Create the account and the opportunity.
        Database.SaveResult[] results = Database.insert(new SObject[] {
            parentAccount, newOpportunity });
        
        // Check results.
        for (Integer i = 0; i < results.size(); i++) {
            if (results[i].isSuccess()) {
            System.debug('Successfully created ID: '
                  + results[i].getId());
            } else {
            System.debug('Error: could not create sobject '
                  + 'for array element ' + i + '.');
            System.debug('   The error reported was: '
                  + results[i].getErrors()[0].getMessage() + '\n');
            }
        }
    }
}

The other way to do it is using composite API: Check the attached link and it has ways to Update an Account, Create a Contact, and Link Them with a Junction Object

You must log in to answer this question.

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