Skip to main content
Requirement: Capture all the failures during a batch process, build an erroneous records CSV file and attach it to the failure email.
Batch Apex: Let’s say you’re running a batch job with 20000 records, salesforce divides this batch into 100 transactions of 200 records each automatically. (Default batch size is 200 records). Let’s say we’ve 1000 erroneous records across those 100 transactions and we don’t know which transactions had errors. How do you maintain the state between these transactions? Database.Stateful allows to maintain state between transactions of a batch job. When using Database.Stateful, only instance member variables retain their values between transactions. Static member variables don’t and are reset between transactions.
For more info: Batch Apex
Scenario: Create a batch process on Accounts, capture the failures in a CSV file and send an email to the batch job creator.
Note: Maximum size for email attachment is 3MB.
Gist Link to the codeBatchApexAccountUpdate.cls
global class BatchApexAccountUpdate implements Database.Batchable<SObject>, Database.Stateful{ 
    global Map<Id, String> errorMap {get; set;}
    global Map<Id, SObject> IdToSObjectMap {get; set;}
 
    global BatchApexAccountUpdate(){
        errorMap = new Map<Id, String>();
        IdToSObjectMap = new Map<Id, SObject>();
    }
 
    global Database.QueryLocator start(Database.BatchableContext BC) { 
        return Database.getQueryLocator('Select Id, OwnerId, Name From Account');
    } 
 
    global void execute(Database.BatchableContext BC, List<SObject> scope) { 
        List<Account> accountList = new List<Account>();
        for(SObject s : scope){
          Account acct = (Account) s;
          // Logic to update fields on the Account
           accountList.add(acct);
        }
 
        if(accountList.size() > 0) {
            List<Database.SaveResult> dsrs = Database.Update(accountList, false);
            Integer index = 0;
            for(Database.SaveResult dsr : dsrs){
                if(!dsr.isSuccess()){
                    String errMsg = dsr.getErrors()[0].getMessage();
                    errorMap.put(accountList[index].Id, errMsg);
                    IdToSObjectMap.put(accountList[index].Id, accountList[index]);
                }
                index++;
            }
        }
    } 
 
    global void finish(Database.BatchableContext BC) { 
       //Send an email to the User after your batch completes 
       if(!errorMap.isEmpty()){
            AsyncApexJob a = [SELECT id, ApexClassId,
                       JobItemsProcessed, TotalJobItems,
                       NumberOfErrors, CreatedBy.Email
                       FROM AsyncApexJob
                       WHERE id = :BC.getJobId()];
            String body = 'Your batch job '
             + 'BatchApexAccountUpdate '
             + 'has finished. \n' 
             + 'There were '
             + errorMap.size()
             + ' errors. Please find the error list attached to the Case.';
 
            // Creating the CSV file
            String finalstr = 'Id, Name, Error \n';
            String subject = 'Account - Apex Batch Error List';
            String attName = 'Account Errors.csv';
            for(Id id  : errorMap.keySet()){
                string err = errorMap.get(id);
                Account acct = (Account) IdToSObjectMap.get(id);
                string recordString = '"'+id+'","'+acct.Name+'","'+err+'"\n';
                finalstr = finalstr +recordString;
            } 
 
            // Define the email
            Messaging.SingleEmailMessage email = new Messaging.SingleEmailMessage(); 
 
            // Create the email attachment    
            Messaging.EmailFileAttachment efa = new Messaging.EmailFileAttachment();
            efa.setFileName(attName);
            efa.setBody(Blob.valueOf(finalstr));
 
            // Sets the paramaters of the email
            email.setSubject( subject );
            email.setToAddresses( new String[] {a.CreatedBy.Email} );
            email.setPlainTextBody( body );
            email.setFileAttachments(new Messaging.EmailFileAttachment[] {efa});
 
            // Sends the email
            Messaging.SendEmailResult [] r = 
                Messaging.sendEmail(new Messaging.SingleEmailMessage[] {email});   
            }
    } 
}

Comments

Popular posts from this blog

Salesforce UNABLE_TO_LOCK_ROW: unable to obtain exclusive access to this record

Unable to lock row - Record currently unavailable errors Description When a record is being updated or created, we place a lock on that record to prevent another operation from updating the record at the same time and causing inconsistencies on the data. These locks normally last for a few seconds and when the lock is released, other operations can do whatever processing they are supposed to do on the record in question. However, a given transaction can only wait a maximum of 10 seconds for a lock to be released, otherwise it will time out. Resolution What and when records get locked depends on the operation you are performing and the main record you are working on. The  Force.com Record Locking Cheatsheet   provides detailed information on this and it's highly recommended that you familiarize yourself with its contents. Common Scenarios that prevent unlocking record a. Email-To-Case When an email is processed by email-to-case, triggers on the email message

How to Build a Basic Salesforce REST API (With Test Class)

How to Build a Basic Salesforce REST API (With Test Class) and Test with POSTMAN Salesforce : Creating Anonymous Apex REST APIs with Force.com The Force.com REST API lets you integration with Force.com applications using standard HTTP methods. This API provides a way to expose the data you have within your Force.com application to external applications – both mobile and non-mobile. A few useful bits of information related to these REST APIs: This sample Code you how to implement a simple REST API to fetch Account in Apex class: @RestResource(urlMapping='/Account/*') global with sharing class MyRestResource { @HttpGet global static Account doGet() { RestRequest req = RestContext.request ; RestResponse res = RestContext.response ; String accountId = req.requestURI. substring ( req.requestURI. lastIndexOf ( '/' ) +1 ) ; Account result = [SELECT Id, Name, Phone, Website FROM Account WHERE Id = : account

Salesforce Callout using Future Method

Hi All, Today's topic , Use of Future method in web services callout. Consider one scenario, Whenever ,We create one Account record or Edit a Account record using UI, Populate Billing city based on Billing Zipcode . Like Below image before save, After Press the save button,Billing city should auto populate. We want to populate Billing city based on Billing zipcode using below API endpoint. Postal PIN Code API Postal PIN Code API allows developers to get details of Post Office by searching Postal PIN Code or Post Office Branch Name of India. It has following format:         1. Get Post Office(s) details search by Postal PIN Code             http://postalpincode.in/api/pincode/{PINCODE}         2.Get Post Office(s) details search by Post Office branch name            http://postalpincode.in/api/postoffice/{POSTOFFICEBRANCHNAME} Postal PIN Code API returns the response in JSON format. "Status" field in response is set to SUCCESS or ERROR, "