[SalesForce] Child to Parent Query: Updating Parent Fields via Apex

I'm new to apex and still trying to figure things out. I'm attempting to pull the Parent data from the Child to then update. I keep receiving the error "Variable does not exist: Location__r".

Please not the custom object singular is "Locations" and the field of opportunity is "Location".

public class OpportunityTriggerHandler {

    //Setting list of opportunities IDs
    public static void opptoAcctModifier(Set<ID>oppsIds)
        {
            //Create list of locations to update
            List<Locations__c> modifiedLocs = new List<Locations__c>();
            //Grabbing Opportunity and Related Locations fields
            for(Opportunity opp : [SELECT Id,Location__c,Locations__r.Status__c,Locations__r.Original_Payment_Amount__c,Locations__r.Date_Payment_Processed__c,Locations__r.Date_Payment_Received__c FROM Opportunity WHERE Id IN :oppsIds])
                {
                    //Locations fields to update and Opportunity fields to pull from
                    opp.Locations__r.Original_Payment_Amount__c = opp.Amount;
                    opp.Locations__r.Date_Payment_Processed__c = opp.CloseDate;
                    opp.Locations__r.Date_Payment_Received__c = opp.CloseDate;
                    opp.Locations__r.Status__C = opp.Billing_Status__c;
                    modifiedLocs.add(opp.Location__c);
                }
            System.debug(modifiedLocs);
            //update modifiedLocs;
        }
}

Best Answer

There are a few things wrong in your provided code, but it should be pretty easy to fix.

Before we get to that though, some brief explanation.

  • Master-Detail and Lookup relationship fields that end in __c (for custom relationships) or Id (e.g. AccountId a "standard" relationship on the Opportunity object) contain the Id of the related record
  • Where you have a relationship field, you also have a pseudo-field ending in __r for custom relationships and dropping the "Id" (e.g. Account instead of AccountId on Opportunity) for most standard relationships
  • This pseudo-field contains a full-fledged SObject instance of your related record
    • This analogy isn't really an intuitive way to think about how to construct the query, but it is an intuitive way to understand the row data returned by the query
  • Using the plural of the field name is typically only done when your query goes in the opposite direction (i.e. from parent object to the child object)

With that out of the way, let's fix your code

public static void opptoAcctModifier(Set<ID>oppsIds){
    List<Locations__c> modifiedLocs = new List<Locations__c>();

    // When querying for fields on a related, parent record, you just change the __c
    //  to __r
    // The plural isn't used in this situation
    for(Opportunity opp : [SELECT Id, Location__c, Location__r.Status__c, Location__r.Original_Payment_Amount__c, Location__r.Date_Payment_Processed__c, Location__r.Date_Payment_Received__c FROM Opportunity WHERE Id IN :oppsIds]){
        // Similarly, when accessing/modifying related record data, we use __r
        // (still no plural)
        opp.Location__r.Original_Payment_Amount__c = opp.Amount;
        opp.Location__r.Date_Payment_Processed__c = opp.CloseDate;
        opp.Location__r.Date_Payment_Received__c = opp.CloseDate;
        opp.Location__r.Status__c = opp.Billing_Status__c;

        // To perform DML, we need a collection of SObjects (Location__c is an SObject)
        // Your list is also defined as a collection of Location__c rather than Id
        // So you want to use opp.Location__r here (which contains a Location__c record)
        modifiedLocs.add(opp.Location__r);
    }
    System.debug(modifiedLocs);
    update modifiedLocs;
}

Querying for parent fields in order to update them seems a little strange to me though. It would certainly work, but querying for the parent fields is unnecessary.

Given that this is code that appears to be run as part of a trigger, the entire query is not strictly required either. The limit of 100 queries per (synchronous) transaction is the most common one people run into, so anything we can do to reduce queries is worth considering.

If I were to write this method, I'd make use of the trigger context variables (passed in as a List to the method) and our ability to use the SObject constructor to set a record Id (which allows us to update a record without querying for it)


public static void opptoAcctModifier(List<Opportunity> opps){
    List<Locations__c> modifiedLocs = new List<Locations__c>();

    for(Opportunity opp :opps){
        // A quick, simple guard clause to prevent issues if an Opp
        //   is not related to a location
        // By passing in trigger.new to this method, we'll automatically
        //   get access to Location__c without querying.
        // That makes unit testing a bit more painful (you need to remember
        //   to query for or include Location__c in the test data you pass in), but
        //  I'm of the opinion that it's a worthwhile tradeoff
        if(opp.Location__c == null){
            // the continue keyword causes us to skip the rest of the loop
            //   and start processing the next record in the collection
            continue;
        }

        // In the SObject constructor, we can set the Id field (which is 
        //   otherwise read-only).
        // We can also set additional fields (separate each field = value pair
        //   with a comma like you're passing arguments to a method), and this
        //   is the fastest way to set fields on an SObject.
        // That speed doesn't really matter though, the bigger gain is that this
        //   ends up requiring a bit less typing
        modifiedLocs.add(new Location__c(
            Id = opp.Location__c,
            Original_Payment_Amount__c = opp.Amount,
            Date_Payment_Processed__c = opp.CloseDate,
            Date_Payment_Received__c = opp.CloseDate,
            Status__c = opp.Billing_Status__c
        ));
    }
    System.debug(modifiedLocs);
    update modifiedLocs;
}
Related Topic