+ Start a Discussion
brozinickrbrozinickr 

Batch Apex Question

Hi,

 

I'm trying to write some batch apex for a basic field update.  The field update pulls the Payment_Type__c from Recon_Detail__c object to the Payment_Type__c field on the Actual__c object.  I'm relating to two by their Opportunity IDs.  I'm having issues instantiating my query to relate both objects together.  Here's the error:

 

Initial term of field expression must be a concrete SObject: String at line 15 column 133 (line underlined and colored in navy below)

 

Here's my code:

 

global class UpdatePaymentTypeonActual implements Database.Batchable<sObject>{
 
    //This is the query that is passed to the execute method.  It queries all of the Actuals that do not
    //have a Payment Type.
    
    String query = 'SELECT id, Opportunity__c, Payment_Type__c FROM Actual__c WHERE Payment_Type__c = null';
 
    global database.queryLocator start(Database.BatchableContext BC) {
        return database.getQueryLocator(query);
 
    } //close start method
 
    global void execute(Database.BatchableContext BC, List<Actual__c> scope) {
 
        List<Recon_Detail__c> reconDetails = [Select id, Opportunity__c, Payment_Type__c From Recon_Detail__c Where Opportunity__c=:query.Opportunity__c limit 1];
 
        // Iterate through the whole query of Actuals with blank Payment Types and insert Payment Type from the Recon Detail.
        for(Actual__c a : scope) {
            if(a.Payment_Type__c = null) {
                a.Payment_Type__c = reconDetails.Payment_Type__c;
            } //close if statement
        } //close for-loop
 
        try {
            update scope;
        } catch (system.dmlexception e) {
            System.debug('Scope not updated: ' + e);
        }
 
    } //close execute method
 
    global void finish(Database.BatchableContext BC) {
 
        AsyncApexJob a = [Select Id, Status, NumberOfErrors, JobItemsProcessed,
            TotalJobItems, CreatedBy.Email
            from AsyncApexJob where Id =
            :BC.getJobId()];
 
        // Create and send an email with the results of the batch.
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
 
        mail.setToAddresses(new String[] {a.CreatedBy.Email});
        mail.setReplyTo('rachelbr@angieslist.com');
        mail.setSenderDisplayName('Salesforce');
        mail.setSubject('Payment Type Update ' + a.Status);
        mail.setPlainTextBody('The batch apex job processed ' + a.TotalJobItems +
        ' batches with ' + a.NumberofErrors + ' failures.  Huzzah.');
 
        Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
 
    } //close finish method
} //close class

 

Best Answer chosen by Admin (Salesforce Developers) 
SLockardSLockard

Are there situations where your Actual__c objects don't have a related Recon_Detail__c? I guess what I'd try next would be this:

 

// Iterate through the whole query of Actuals with blank Payment Types and insert Payment Type from the Recon Detail.
        for(Actual__c a : scope) {
            if(a.Payment_Type__c == null && rdMap.containsKey(a.Opportunity__c)) {
               a.Payment_Type__c = rdMap.get(a.Opportunity__c).Payment_Type__c;  --hopefully no more failure at this line
             } //close if statement
        } //close for-loop

 

All Answers

SLockardSLockard

You need to have a list/set of the actual__c opportunity__c Ids and check for reconDetails with Opportunity__c in that list/set. Also, you need to find the right reconDetail for each actual__c, so try this updated code:

 

global class UpdatePaymentTypeonActual implements Database.Batchable<sObject>{
 
    //This is the query that is passed to the execute method.  It queries all of the Actuals that do not
    //have a Payment Type.
    
    String query = 'SELECT id, Opportunity__c, Payment_Type__c FROM Actual__c WHERE Payment_Type__c = null';
 
    global database.queryLocator start(Database.BatchableContext BC) {
        return database.getQueryLocator(query);
 
    } //close start method
 
    global void execute(Database.BatchableContext BC, List<Actual__c> scope) {
		Set<Id> actualOpps = new Set<Id>();
		for (Actual__c a : scope)
		{
			actualOpps.add(a.Opportunity__c);
		}
        List<Recon_Detail__c> reconDetails = [Select id, Opportunity__c, Payment_Type__c From Recon_Detail__c Where Opportunity__c IN :actualOpps];
		Map<Id, Recon_Detail__c> rdMap = new Map<Id, Recon_Detail__c>();
		for (Recon_Detail__c rD : reconDetails)
		{
			rdMap.put(rD.Opportunity__c, rD);
		}
        // Iterate through the whole query of Actuals with blank Payment Types and insert Payment Type from the Recon Detail.
        for(Actual__c a : scope) {
            if(a.Payment_Type__c == null) {
                a.Payment_Type__c = rdMap.get(a.Opportunity__c).Payment_Type__c;
            } //close if statement
        } //close for-loop
 
        try {
            update scope;
        } catch (system.dmlexception e) {
            System.debug('Scope not updated: ' + e);
        }
 
    } //close execute method
 
    global void finish(Database.BatchableContext BC) {
 
        AsyncApexJob a = [Select Id, Status, NumberOfErrors, JobItemsProcessed,
            TotalJobItems, CreatedBy.Email
            from AsyncApexJob where Id =
            :BC.getJobId()];
 
        // Create and send an email with the results of the batch.
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
 
        mail.setToAddresses(new String[] {a.CreatedBy.Email});
        mail.setReplyTo('rachelbr@angieslist.com');
        mail.setSenderDisplayName('Salesforce');
        mail.setSubject('Payment Type Update ' + a.Status);
        mail.setPlainTextBody('The batch apex job processed ' + a.TotalJobItems +
        ' batches with ' + a.NumberofErrors + ' failures.  Huzzah.');
 
        Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
 
    } //close finish method
} //close class

 I hope that helps.

brozinickrbrozinickr

Works like a charm!  Thank you for your help! :)

brozinickrbrozinickr

Hi SLockard,

 

I've been trying to execute  this batch job and it keeps on failing.  It says that it's trying to dereference a null object on the line I marked below in my code.  I'm thinking that it doesn't seem to like something in the mapping.

 

global class UpdatePaymentTypeonActual implements Database.Batchable<sObject>{
 
    //This is the query that is passed to the execute method.  It queries all of the Actuals that do not
    //have a Payment Type.
    
    String query = 'SELECT id, Opportunity__c, Payment_Type__c FROM Actual__c';
 
    global database.queryLocator start(Database.BatchableContext BC) {
        return database.getQueryLocator(query);
 
    } //close start method
 
    global void execute(Database.BatchableContext BC, List<Actual__c> scope) {
        Set<Id> actualOpps = new Set<Id>();
        for (Actual__c a : scope)
        {
            actualOpps.add(a.Opportunity__c);
        }
        List<Recon_Detail__c> reconDetails = [Select id, Opportunity__c, Payment_Type__c From Recon_Detail__c Where Opportunity__c IN :actualOpps];
        Map<Id, Recon_Detail__c> rdMap = new Map<Id, Recon_Detail__c>();
        for (Recon_Detail__c rD : reconDetails)
        {
            rdMap.put(rD.Opportunity__c, rD);
        }
        // Iterate through the whole query of Actuals with blank Payment Types and insert Payment Type from the Recon Detail.
        for(Actual__c a : scope) {
            if(a.Payment_Type__c == null) {
                a.Payment_Type__c = rdMap.get(a.Opportunity__c).Payment_Type__c; 

//failing line

            } //close if statement
        } //close for-loop
 
        try {
            update scope;
        } catch (system.dmlexception e) {
            System.debug('Scope not updated: ' + e);
        }
 
    } //close execute method
 
    global void finish(Database.BatchableContext BC) {
         
        AsyncApexJob a = [Select Id, Status, NumberOfErrors, JobItemsProcessed,
            TotalJobItems, CreatedBy.Email
            from AsyncApexJob where Id =
            :BC.getJobId()];
 
        // Create and send an email with the results of the batch.
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
 
        mail.setToAddresses(new String[] {a.CreatedBy.Email});
        mail.setReplyTo('rachelbr@angieslist.com');
        mail.setSenderDisplayName('Salesforce');
        mail.setSubject('Payment Type Batch Apex Load is' + a.Status);
        mail.setPlainTextBody('The batch apex job processed ' + a.TotalJobItems +
        ' batches with ' + a.NumberofErrors + ' failures.');
 
        Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
 
    } //close finish method
} //close class

 

SLockardSLockard

That may be from records with null values in those fields ... try changing your query to this and see if that helps..

 

String query = 'SELECT id, Opportunity__c, Payment_Type__c FROM Actual__c WHERE Opportunity__c != null AND Payment_Type__c != null';

 

brozinickrbrozinickr

Sweet, I got it to run successfully with that code but I do want it to get actuals that do have a null payment type.  I omitted the payment_type__c in the query and ran it again and  it fails with the same error.  How can I grab the nulls without it failing? 

SLockardSLockard

Sorry about that, I meant to say change the other query like so:

 

List<Recon_Detail__c> reconDetails = [Select id, Opportunity__c, Payment_Type__c From Recon_Detail__c Where Opportunity__c IN :actualOpps AND Payment_Type__c != null];

 Because this will stop the error of trying to set the Actuals field to a null value.

brozinickrbrozinickr

Okie dokie.  I tried that and now it's saying that I am dereferencing a null object on line 31.  Do I just need to add an else statement to account for the other situation at the end of the if payment type is equal to null statement before that line?

 

global class UpdatePaymentTypeonActual implements Database.Batchable<sObject>{
 
    //This is the query that is passed to the execute method.  It queries all of the Actuals that do not
    //have a Payment Type and find their Opportunity ID.
    
    String query = 'SELECT id, Opportunity__c, Payment_Type__c FROM Actual__c WHERE Opportunity__c != null';
 
    global database.queryLocator start(Database.BatchableContext BC) {
        return database.getQueryLocator(query);
 
    } //close start method
 
    global void execute(Database.BatchableContext BC, List<Actual__c> scope) {
        Set<Id> actualOpps = new Set<Id>();
        for (Actual__c a : scope)
        {
            actualOpps.add(a.Opportunity__c);
        }
        
       List<Recon_Detail__c> reconDetails = [Select id, Opportunity__c, Payment_Type__c From Recon_Detail__c Where Opportunity__c IN :actualOpps AND Payment_Type__c != null];
        
        Map<Id, Recon_Detail__c> rdMap = new Map<Id, Recon_Detail__c>();
        for (Recon_Detail__c rD : reconDetails)
        {
            rdMap.put(rD.Opportunity__c, rD);
        }
       
        // Iterate through the whole query of Actuals with blank Payment Types and insert Payment Type from the Recon Detail.
        for(Actual__c a : scope) {
            if(a.Payment_Type__c == null) {
               a.Payment_Type__c = rdMap.get(a.Opportunity__c).Payment_Type__c;  --failure at this line
} //close if statement } //close for-loop try { update scope; } catch (system.dmlexception e) { System.debug('Scope not updated: ' + e); } } //close execute method global void finish(Database.BatchableContext BC) { AsyncApexJob a = [Select Id, Status, NumberOfErrors, JobItemsProcessed, TotalJobItems, CreatedBy.Email from AsyncApexJob where Id = :BC.getJobId()]; // Create and send an email with the results of the batch. Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage(); mail.setToAddresses(new String[] {a.CreatedBy.Email}); mail.setReplyTo('rachelbr@angieslist.com'); mail.setSenderDisplayName('Salesforce'); mail.setSubject('Payment Type Batch Apex Load is' + a.Status); mail.setPlainTextBody('The batch apex job processed ' + a.TotalJobItems + ' batches with ' + a.NumberofErrors + ' failures.'); Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail }); } //close finish method } //close class

 

 

SLockardSLockard

Are there situations where your Actual__c objects don't have a related Recon_Detail__c? I guess what I'd try next would be this:

 

// Iterate through the whole query of Actuals with blank Payment Types and insert Payment Type from the Recon Detail.
        for(Actual__c a : scope) {
            if(a.Payment_Type__c == null && rdMap.containsKey(a.Opportunity__c)) {
               a.Payment_Type__c = rdMap.get(a.Opportunity__c).Payment_Type__c;  --hopefully no more failure at this line
             } //close if statement
        } //close for-loop

 

This was selected as the best answer
brozinickrbrozinickr

Yeah we just started adding Recon Details to our org (basically its a table of all recently published and about to be published contracts) so it's entirely plausible that we have Actuals without Recon Details.

 

I just executed the batch in my dev console and it worked beautifully!  :)  Thank you soo much for helping me for this.  I literally don't know what I would have done without your help! 

SLockardSLockard

No problem, I'm just glad I could help out!