SFDC Stop - Always the latest about Salesforce


Full Tutorial Series with videos, free apps, live sessions, salesforce consulting and much more.


Telegram logo   Join our Telegram Channel
Showing posts with label Error. Show all posts
Showing posts with label Error. Show all posts

Thursday, 25 April 2024

Building Blocks of Apex: Variables & Data Types Explained Simply - Salesforce Apex Tutorial Part 2 to 7 | Code Snippet

Hello Trailblazers,

In this post, I'm sharing the code snippet for Coding with Confidence: The Fun Way to Learn Salesforce Apex tutorial series part 2 to 7 below:
// * Variables & Data Types in Apex

/*

? What is a variable?
* A variable is simply a named storage/memory location that contains a value. In apex, all variables have a "data type".
! Each variable is by default initialized to null

? What is a Data Type?
* Data type is a keyword associated with a variable. It basically represents the type of data which that variable will store.
* List of primitive data types:
* 1. Boolean
* 2. Integer
* 3. Decimal
* 4. Double
* 5. Long
* 6. Date
* 7. Datetime
* 8. Time
* 9. Id
* 10. String
* 11. Object
* 12. Blob

* These are the OOTB or fundamental data types available in apex programming language. Let's talk about each one of them in detail:

? What is a Boolean data type?
* A variable of Boolean data type can store only one of these two values:
* 1. True
* 2. False

* Example of Boolean data type:
*/

/*
one
two
three

*/

// comment

// * In order to declare a variable, we follow the format: <Data Type><space><Variable name><;>
Boolean isQualified;
System.debug('Value of isQualified boolean variable is: ' + isQualified);

isQualified = false; // * Equals (=) means Assignment
System.debug('Value of isQualified boolean variable is: ' + isQualified);

isQualified = true;
System.debug('Value of isQualified boolean variable is: ' + isQualified);

// isQualified = 123;

/*

? What is an Integer data type?
* An Integer data type can store a 32-bit number with no decimal point. It can store value between -2,147,483,648 to 2,147,483,647

* Example of Integer data type:

*/

Integer sum;
System.debug('Value of sum Integer variable is: ' + sum);

sum = 1;
System.debug('Value of sum Integer variable is: ' + sum);

sum = 10;
System.debug('Value of sum Integer variable is: ' + sum);

sum = 2147483647;
System.debug('Value of sum Integer variable is: ' + sum);

sum = -2147483647;
System.debug('Value of sum Integer variable is: ' + sum);

// sum = false;

/*

? What is a Decimal data type?
* A Decimal data type variable can store a number with a decimal point.

* Example of Decimal data type:

*/

Decimal sum;
System.debug('Value of sum Decimal variable is: ' + sum);

sum = 1.0;
System.debug('Value of sum Decimal variable is: ' + sum);

sum = 1.01;
System.debug('Value of sum Decimal variable is: ' + sum);

sum = 1.234567890123456789012345678901234567890123456789;
System.debug('Value of sum Decimal variable is: ' + sum);

sum = -123456789012345678901234567890123456789012345678.9;
System.debug('Value of sum Decimal variable is: ' + sum);

//* Help Article about the field: https://help.salesforce.com/s/articleView?id=000387302&type=1, however we can have 49 digits in total for a variable of type decimal

// * Assiging integer to a decimal
sum = 123;
System.debug('Value of sum Decimal variable is: ' + sum);

// sum = true;

/*
? What is a Double data type?
* A Double data type variable can store a 64-bit number with a decimal point. It can store value between -2^63 to 2^63-1
* Example of Double data type:
*/

Double sum = 3.14;
System.debug('Value of sum Double variable is: ' + sum);

// * Use Double for less precision. For example: Scientific calculations where rounding off can be done. Use Decimal for more precision. For example: Financial calulations where there are no chances of errors. By default, currency fields in Salesforce are stored as decimals.

/*
? What is a Long data type?
* A Long data type variable can store a 64-bit number without a decimal point. It can store value between -2^63 to 2^63-1.
* Example of Long data type:
*/

Long long1 = 1000000000;  // * Valid long value without "L"
Long long2 = 9223372036854775807L;  // * Valid long value with "L" (for clarity)
System.debug('Value of long1 Long variable is: ' + long1);
System.debug('Value of long2 Long variable is: ' + long2);

// * Long can be used when we need a higher range as compared to an integer

/*
? What is a Date data type?
* A Date data type variable can store a date value without any information about time.
* Example of Date data type:
*/

Date date1 = Date.newInstance(1994, 12, 26);
System.debug('Value of date1 Date variable is: ' + date1);
System.debug('Subtracting 1 from date1 variable. Updated value = ' + (date1 - 1));
System.debug('Adding 1 to date1 variable. Updated value = ' + (date1 + 1));

Date dateFromString = Date.valueOf('1994-12-26');
System.debug('Value of dateFromString Date variable is: ' + dateFromString);

System.debug('Date in string without timestamp is: ' + String.valueOf(dateFromString));

System.debug('Today\'s Date is: ' + System.today());

// * Adding two date values
Date date1 = Date.newInstance(1994, 12, 26);
Date date2 = Date.newInstance(1994, 12, 20);
System.debug(date1 + date2);

/*
? What is a Datetime data type?
* A Datetime data type variable can store a datetime value like a timestamp.
* Example of Datetime data type:
*/

Datetime date1 = Datetime.newInstance(1994, 12, 26);
System.debug('Value of date1 Datetime variable is: ' + date1);

Datetime dateFromString = Datetime.valueOf('1994-12-26 23:11:10'); // GMT + 5:30
System.debug('Value of dateFromString Datetime variable is: ' + dateFromString);

System.debug('Date in string without timestamp is: ' + String.valueOf(dateFromString));

/*
? What is a Time data type?
* A Time data type variable can store a time value which indicates a particular time.
* Example of Time data type:
*/

Time time1 = Time.newInstance(14, 25, 60, 100);
System.debug('Value of time1 Time variable is: ' + time1);
System.debug('Value of hour: ' + time1.hour());
System.debug('Value of minute: ' + time1.minute());
System.debug('Value of second: ' + time1.second());
System.debug('Value of millisecond: ' + time1.millisecond());

/*
? What is a ID data type?
* An ID data type variable can store any valid 18-character salesforce record id.
* Example of ID data type:
*/

Id accountId = '001Hy00001D07fwIAB';
System.debug('Value of Id accountId = ' + accountId);

Id accountIdFifteenDigits = '001Hy00001D07fw';
System.debug('Value of Id accountIdFifteenDigits = ' + accountIdFifteenDigits);

accountId = '123456789123456789';

/*
? What is a String data type?
* A String data type variable can store any set of characters surrounded by single quotes. It has no limit on the number of characters, heap size limit is used instead
* Example of String data type:
*/

String name = 'SFDC Stop';
System.debug('Value of String name = ' + name);

String nameWithTrailingWhitespace = 'SFDC Stop   ';
System.debug('Value of String nameWithTrailingWhitespace = ' + nameWithTrailingWhitespace);

String emptyStringWithWhitespace = '     ';
System.debug('Value of String emptyStringWithWhitespace = ' + emptyStringWithWhitespace);

String escapeSequence = '\nSFDC\tStop \\ A blog about salesforce';
System.debug('Value of String escapeSequence = ' + escapeSequence);

String a = 'apple', b = 'mango';
System.debug(a<b);

String c = 'sfdc', d = 'Sfdc';
System.debug(c==d);

/*
? What is a Object data type?
* An Object data type variable can store value of any data type that is supported in apex. All data types inherit from object
* Example of Object data type:
*/

Object num = 10;
System.debug('Value of Object num = ' + num);

Object name = 'SFDC Stop';
System.debug('Value of Object name = ' + name);

Object num1 = 10, num2 = 20;
System.debug('Sum of Object num1 and num2 = ' + (num1 + num2));

Object num1 = 10, num2 = 20;
Integer num1Int = (Integer) num1, num2Int = (Integer) num2;
System.debug('Sum of Integer num1Int and num2Int = ' + (num1Int + num2Int));

/*
? What is a Blob data type?
* An Blob data type variable can store a collection of binary data as a single object. Blob is the short form for Binary Large Object.
* Example of Blob data type: Contents of an image. Blob can be converted to a String and String can be converted to a Blob
*/

Blob b1 = Blob.valueOf('test');
System.debug('Value of Blob b1 = ' + b1);

System.debug('Value of Blob b1 as string = ' + b1.toString());

YouTube Tutorials

You can check out the tutorials below as well:

Building Blocks of Apex: Variables & Data Types Explained Simply - Salesforce Apex Tutorial Part 2

Apex Gotchas! Common Variable Errors (and Writing Comments) - Salesforce Apex Tutorial Part 3

Apex Integer Data Types: Understanding Limits & Avoiding Errors - Salesforce Apex Tutorial Part 4

Unlock Precision in Apex: The Beginner Guide to Decimal Data Type - Salesforce Apex Tutorial Part 5

Apex Double, Long, Date, Datetime & Time Data Types Explained - Salesforce Apex Tutorial Part 6

Apex Id, String, Object and Blob Data Types Decoded - Salesforce Apex Tutorial Part 7

Check out the tutorials and let me know your feedback in the comments down below.

Happy Trailblazing!!

Sunday, 24 March 2024

FlowError: The number of results does not match the number of flow interviews | Building invocable actions The Right Way!

Hello Trailblazers,

In this post we're going to learn, how we can build apex invocable actions The Right Way. It might be possible that you've faced this issue in the past:

FlowError: The number of results does not match the number of flow interviews

Today, we're going to build an apex invocable action, call it from flow in a scenario such that it fails and then fix the error to make sure it works perfectly fine. Let's Begin!

Use Case

We have a custom object named Application (Application__c), the details of that object are provided below:
As you can see above, our name field is Application ID which is an auto number. We have 3 custom fields:

1. Candidate Name (CandidateName__c): This is a text field with length 255. It'll store the name of our candidate who has submitted the application
2. Candidate Phone (CandidatePhone__c): This is a phone field. It'll store the phone number of our candidate
3. Lead (Lead__c): This is a lookup to standard lead object. For each new application, if the candidate is not present as a lead in salesforce, we'll create a new lead in salesforce and will tag that lead to our application record. If a lead is already present in salesforce, we'll tag that existing lead to our application record as well.

Automation

How do we check whether we should create a new lead or use an existing lead?

We're going to do that using candidate's phone number in the application. If there exists a lead with the same phone number as that of a candidate, we're going to tag that lead to the application record as it's created. Otherwise, we're going to create a new lead with that phone number and candidate's name and we're going to tag that lead to our application record.

So, we can say that our Application's CandidatePhone__c will map to Lead's Phone field. We need an automation (flow) to create lead records automatically and tag them to application records as these are created. We'll create that flow soon.

Platform Encryption

For security reasons, we've turned on platform encryption in our salesforce org and the lead's Phone field is encrypted as shown below:
If we click on Select Fields button above in Encrypt Standard Fields section, we'll get the below page, where we've encrypted Lead's Phone field as shown below:
This encryption will create one challenge in building our flow, which we'll see soon. Let's build a flow now.

Building a Flow

We need a Record Triggered flow which will fire when an application record is created. It'll check if an existing lead record is present with Phone = CandidatePhone__c or not. If yes, it'll tag that record with the application record. Otherwise, it'll create one new lead record using CandidatePhone__c and CandidateName__c and will tag that new lead record to our application record.

Got to Setup, search for flow, and click on the New Flow button as shown below:
You'll get the below screen:
Choose Record-Triggered flow as shown above and click Create.

Choose the object as Application, this flow will run when an application record is created, in After context as shown below:
Click on the Save button to save the flow. The details are provided below:
Flow Label: Tag Lead to Application
Flow API Name: Tag_Lead_to_Application
Description: This flow will tag a lead record to the application record as it's created.

Click on Save button in the dialog to save the flow.

Now, our flow is saved. The first step is to query lead records where Phone = CandidatePhone__c. We're going to use a Get element to query records as shown below:
We only need the Lead Id here, so we're querying only that:

Problem in the Flow

Let's save the flow once by clicking the Save button. As you try to save the flow, you'll get an error as shown below:
It says: Because Lead's Phone field is encrypted, our flow cannot reference that field. We need to remove this reference in order to save the flow. This means that we cannot query our existing lead records by filtering through Phone field. In order to achieve this functionality now, it's time to move to Invocable Apex.

Resolution: Invocable Apex

We're going to create a new apex class which will help us in querying the leads by phone number. The code for the same is provided below:
public class LeadQueryAction {

    /**
    * This method will query leads by phone numbers and return the list of lead ids
    */
    @InvocableMethod(label='Get Leads by Phone Numbers' description='Return lead records filtered by phone numbers' category='Lead')
    public static List<Id> queryLeadsByPhoneNumbers(List<String> phoneNumbers) {
        Map<Id, Lead> leadsMap = new Map<Id, Lead>([SELECT Id FROM Lead WHERE Phone IN :phoneNumbers]);
        System.debug(phoneNumbers);
        System.debug(leadsMap.keySet());
        return new List<Id>(leadsMap.keySet());
    }
}
As you can see above, we have a class named LeadQueryAction. This class has a method named queryLeadsByPhoneNumbers(List<String> phoneNumbers). This method will receive the list of phone numbers from flow, will query the leads using those phone nunbers and will return the list of lead ids. We're querying lead records using SOQL query: [SELECT Id FROM Lead WHERE Phone IN :phoneNumbers];. We're converting the queried lead records list directly into a Map<Id, Lead> named leadsMap so that we can get the lead ids easily using this map. Finally, we're returning the list of lead ids by converting lead ids set into a list using new List<Id>(leadsMap.keySet());

Also, note that in the @InvocableMethod annotation, we've specified the label as Get Leads by Phone Numbers and description as Return lead records filtered by phone numbers. We've also specified the category as Lead. This label will be visible in our flow when we'll search for our invocable action.

Our apex class is ready now, let's get back to our flow and call this method.

First of all, let's delete the Get element as we're going to query lead records using our invocable apex method now:

Click on the + icon again and search for Get Leads by Phone Numbers action as shown below:
Notice that our invocable method's label and description are visible here. Choose this action and you'll get the New Action dialog as shown below:
Alternatively, you can also search for action, click the Action option and then search for Get Leads by Phone Numbers action as shown below:
You'll get the new Action dialog here as well as shown below:
In our New Apex Action dialog, fill the following information as shown below:
Label: Get Existing Leads
API Name: Get_Existing_Leads
Description: This element will get ids of existing lead records where Phone = CandidatePhone__c

Click on Include button to pass information to phoneNumbers parameter of our invocable apex method and let's bind it with CandidatePhone__c field of our current application record {!$Record.CandidatePhone__c}.

Click on Done.

Now, our Get Existing Leads apex action will provide us the id of the lead (if it exists) which is present in our salesforce org where Phone = CandidatePhone__c for our current application record.

We need to check, if a lead id is returned by our method or not. If a lead id is returned, the existing lead id will be populated in our application's Lead__c lookup. Otherwise, we'll create a new lead record and will populate it's id in our application's Lead__c lookup field.

To check this, we are going to add a decision element as shown below:

Label: Is lead present?
API Name: Is_lead_present
Description: This decision element will check if an existing lead with the candidate's phone number is present in the system or not

Outcomes will be Yes and No (the default outcome from the decision is renamed to No). For the outcome to be Yes, we're checking that the Text from Get_Existing_Leads {!Get_Existing_Leads} (which is nothing but our Lead Id) should not be Blank.

If the outcome of the decision is Yes, we'll update our applicaton record using an update element as shown below:

We'll choose Update Triggering Record option as we want to update lead id on the same record which fired the flow:

The details added in this Update Triggering Record element are provided below:

Label: Update existing lead id on application
API Name: Update_existing_lead_id_on_application
Description: This element will update the Lead__c on application with the lead id received from Get Existing Leads invocable action
How to Find Records to Update and Set Their Values: Use the application record that triggered the flow
Set Field Values for the Application Record: Lead__c = {!Get_Existing_Leads}

If outcome of the decision is No, we'll create a new Lead record and tag that to the application record as shown below:

We'll add a Create Records element in our No path as shown above. The details added in the Create Records element are provided below:

Label: Create new lead
API Name: Create_new_lead
Description: This element will create a new lead record using the candidate details from application record
How Many Records to Create: One
How to Set the Record Fields: Use separate resources, and literal values
Object: Lead
Set Field Values for the Lead:
LastName = {!$Record.CandidateName__c}
Phone = {!$Record.CandidatePhone__c}

As Company field is mandatory on lead, let's add that as well in our Create Records element with a value as SFDC Stop as shown below:
Company = SFDC Stop
This element will create a new lead record for us. Let's update our application record with this lead id. For this, we'll add an Update Triggering Record element again as shown below:
The details added in the Update Triggering Record element are provided below:

Label: Update new lead id on application
API Name: Update_new_lead_id_on_application
Description: This element will update the Lead__c on application with the lead id received from Create new lead action
How to Find Records to Update and Set Their Values: Use the application record that triggered the flow
Set Field Values for the Application Record: Lead__c = {!Create_new_lead}

Let's save our flow by clicking the Save button.
Once saved, activate the same by clicking the Activate button.
Now, it's time to test our flow!

Let's go to the Applications tab in our salesforce org and create a new Application record as shown below:
We've kept the Lead lookup as empty. The Candidate Name is Richard Hendricks and Candidate's Phone is 9999999999. As we click on Save, our flow will create a new lead record for us, as a lead record with this phone number is not already present in salesforce and it'll tag the newly created lead to our application record as shown below:
As you can see above, a lead record named Richard Hendricks is tagged. The Phone is the same as Candidate Phone in our application record and the Company is SFDC Stop.

Now, let's try creating another application record using the same details as shown below:
You'll notice that as the record is created, the same (already existing) lead is tagged to this application record:
Note: You can verify the lead ids of both the lead records tagged to application records to make sure that the same lead record is tagged to both the application records.

This means that our flow and invocable apex method is working perfectly fine. But What will happen if we have more than one application records? Let's test it!

Flow: Bulk Testing

Let's consider a scenario where we're creating two application records in a single transaction. Both the application records should be connected to the same lead record. The code snippet to create these applications is provided below:
List<Application__c> applications = new List<Application__c>();
applications.add(new Application__c(CandidateName__c = 'Richard Hendricks', CandidatePhone__c = '9999999999'));
applications.add(new Application__c(CandidateName__c = 'Richard Hendricks', CandidatePhone__c = '9999999999'));
insert applications;
As you can see above, we're inserting a list of applications with 2 records. Both the records are by the same candidate, so, ideally the same lead record should be tagged to both the applications. When we execute this code snippet, we get the result as shown below:
As you can see above, our flow is failing and we're getting an error. In our case, it's not showing the exact issue but in some scenarios it'll show the actual error like: FlowError: The number of results does not match the number of flow interviews

This error basically means that the invocable method is not returning the list of same size as it was passed to the method while calling it from flow. Let's debug this by using the below code snippet:
List<String> phoneNumbers = new List<String>{'9999999999','9999999999'};
System.debug(LeadQueryAction.queryLeadsByPhoneNumbers(phoneNumbers));
In the above snippet, we're calling our invocable action method directly and passing a list of two phone numbers which are exactly the same as of our Richard Hendricks lead record. The output when the above code snippet is executed is provided below:
If you remember, our invocable method has some System.debug() statements, I'm sharing those again below for your reference:
The first debug statement is displaying the list of phone numbers received by our invocable method in parameter. It's showing both the phone numbers as: (9999999999, 9999999999). This is perfectly all right. 

We queried our lead records using these phone numbers and got the following ids in our leadsMap.keySet() which is used in the second debug statement: {00QH40000010giZMAQ}. As you can see, we're getting only one lead id in this set as there's a single lead record present in the system with this phone number.

The third debug statement which is coming from System.debug(LeadQueryAction.queryLeadsByPhoneNumbers(phoneNumbers)); is showing the same lead id in a list instead of a set as: (00QH40000010giZMAQ). This means that the list of lead ids which is returned from our invocable method is having only one lead id, however, we passed 2 phone numbers in this method to query and get our related lead records. 

This is why our flow is failing because when our flow is running for 2 records created in the same transaction, we have 2 flow interviews. As the flow is bulkified, the phone numbers list passed to our invocable method consist of 2 phone numbers however our method returned only a single lead id in the list of ids. Therefore, The number of results (lead ids) does not match the number of flow interviews. These metrics (debug statements) are also visible in the earlier logs when we tried to create our application records as shown below:
You can see above that the phoneNumbers list has two phone numbers whereas the leadsMap.keySet() has a single lead id.

In order to make our invocable method work correctly, we need to return the list of lead ids of same size as the size of our input list i.e. the list of phone numbers. Let's fix our invocable method now.

Issue Resolution for "FlowError: The number of results does not match the number of flow interviews"

In order to fix our invocable method, we'll do the following:

1. Query our lead records.
2. Create a map of lead phone to lead id
3. Loop our phone numbers and check: If we have a lead id corresponding to the current phone number, add it to the lead ids list, otherwise, add null to the lead ids list.
4. Return the list of lead ids.

This will ensure that the order of phone numbers received and the lead ids corresponding to those phone numbers remains aligned, it's important to ensure that our flow behaves correctly. The updated code for our invocable action class is provided below:
public class LeadQueryAction {

    /**
    * This method will query leads by phone numbers and return the list of lead ids
    */
    @InvocableMethod(label='Get Leads by Phone Numbers' description='Return lead records filtered by phone numbers' category='Lead')
    public static List<Id> queryLeadsByPhoneNumbers(List<String> phoneNumbers) {
        List<Id> leadIds = new List<Id>();
        List<Lead> leads = [SELECT Id, Phone FROM Lead WHERE Phone IN :phoneNumbers];
        Map<String, Id> leadPhoneNumberToIdMap = new Map<String, Id>();
        for(Lead lead : leads) {
            leadPhoneNumberToIdMap.put(lead.Phone, (Id) lead.Id);
        }
        for(String phoneNumber : phoneNumbers) {
            if(leadPhoneNumberToIdMap.containsKey(phoneNumber)) {
                leadIds.add(leadPhoneNumberToIdMap.get(phoneNumber));
            } else {
                leadIds.add(null);
            }
        }
        return leadIds;
    }
}
As you can see above, we created a list of lead ids named leadIds. This list will store our lead ids which we'll return from our method. Then we queried the lead records as we were doing earlier. This time, we're storing it in a list named leads.

Now, we created a Map<String, Id> named leadPhoneNumberToIdMap. This map will store our lead phone number and the corresponding lead id. We looped all the queried lead records to populate this map. Finally, we looped the phone numbers which we received in our method, we then checked: if our leadPhoneNumberToIdMap contains the current phone number as a key, we'll add the corresponding lead id to the leadIds list, otherwise, we'll add null to our leadIds list. Adding null basically means that for the current phone number, we don't have any lead present in salesforce. Note that a list can have multiple null values.

Let's save our updated apex class and run the below code snippet to test our flow once again. This time we'll create 4 application records as shown below:
List<Application__c> applications = new List<Application__c>();
applications.add(new Application__c(CandidateName__c = 'Richard Hendricks', CandidatePhone__c = '9999999999'));
applications.add(new Application__c(CandidateName__c = 'Erlich Bachman', CandidatePhone__c = '9999999991'));
applications.add(new Application__c(CandidateName__c = 'Richard Hendricks', CandidatePhone__c = '9999999999'));
applications.add(new Application__c(CandidateName__c = 'Gavin Belson', CandidatePhone__c = '9999999992'));
insert applications;
If you notice above, the first and third application records have the same candidate Richard Hendricks with the same phone number as: 9999999999, so, our flow should link the existing lead to both the application records. However, the second and fourth application records have different candidates i.e. Erlich Bachman and Gavin Belson with different phone numbers. So, our flow should create new leads and link them with our application records.

Currently, we only have 2 application records in the system that we created earlier as shown below:

Now, as we execute our code snippet, we get the below output:
Notice that we got 4 new applications created as shown above. The first and third application records are linked with the same lead Richard Hendricks, however, the second and fourth records are linked with new lead records i.e. Erlich Bachman and Gavin Belson

This time our invocable method is working Perfectly Fine! and the flow is not failing. You can add debug statements in this method to see how it works. However, I'm displaying the output we get from our invocable method as it's called internally using the below code snippet:
List<String> phoneNumbers = new List<String>{'9999999999', '9999999993', '9999999999', '9999999994'};
System.debug(LeadQueryAction.queryLeadsByPhoneNumbers(phoneNumbers));
If you notice above, the first and third phone numbers are the same i.e. 9999999999 however, the second and fourth phone numbers are different i.e. 9999999993 and 9999999994 as was there in our application records. I didn't use 9999999991 and 9999999992 here because now we have leads present in the system linked with these phone numbers. When we execute this code snippet, the output list of lead ids which we got from our method is as shown below:
As you can see above, in our output, the first and third entry in our list is the lead id of Richard Hendricks lead and it's the same. However, the second and fourth entry in our list is coming as null because there are no leads for these phone numbers present in salesforce.

Therefore, we passed 4 phone numbers and got a list of 4 elements i.e. lead ids. Now, we can say that: The number of results match the number of flow interviews.

That's all for this tutorial everyone, I hope you liked it, let me know your feedback in the comments down below.

Happy Holi! Get my Mastering Lightning Datatable in Salesforce LWC course at best price using the coupon code "HAPPYHOLI". You can get the same by clicking this link: https://www.udemy.com/course/mastering-lightning-datatable-in-salesforce-lwc/?couponCode=HAPPYHOLI

Happy Trailblazing!!

Sunday, 24 October 2021

System.DmlException: Insert/Upsert failed. First exception on row 1; first error: INVALID_FIELD, Cannot specify both an external ID reference Parent and a salesforce id, ParentId: []

Hello Trailblazers,


In this post we're going to see the solution of the error "System.DmlException: Insert failed. First exception on row 1; first error: INVALID_FIELD, Cannot specify both an external ID reference Parent and a salesforce id, ParentId: []" OR "System.DmlException: Upsert failed. First exception on row 1; first error: INVALID_FIELD, Cannot specify both an external ID reference Parent and a salesforce id, ParentId: []"


The most common scenario for this error is when we're trying to insert both the parent and child records at the same time and we're providing both - the Salesforce ID of the parent object and the parent object record reference with External ID, mentioned in the child record. Ideally we should only provide one out of Salesforce ID of parent object or parent object reference with External ID but not both. Let's have a look at an example.


Inserting Account and Contact record together

You must be aware of the fact that we can use external ids to create a parent-child relationship automatically while inserting new records in salesforce. For ex: In case of account and contact, you don't need to insert account and then get it's record id to insert contact, in order to link it with earlier inserted account record. You can leverage external id on account to insert both records in a single transaction. Let's see how:

As you can see in the above snippet, I've created a list of sObjects in which I have added one account record and one contact record. I have created an external id field on account with API name as: Reference_ID__c. In case of contact record, I have referred to the parent account record by populating the relationship field "Account" (will be Field_Name__r in case of custom object) with an instance of account object consisting of external id only. Using this we're telling salesforce that this contact is linked to the account with reference id as 123. The above code snippet worked perfectly fine upon execution and as a result, account and contact records are created in salesforce. Give it a try!!

Let's do a very small change now:

As you can see above, now we've also specified AccountId in contact record and that too as null which probably should have no impact. Let's execute the code and see what we get:


As you can see we're getting the same error System.DmlException: Upsert failed. First exception on row 1; first error: INVALID_FIELD, Cannot specify both an external ID reference Account and a salesforce id, AccountId: []

As per the post heading error message: "System.DmlException: Insert/Upsert failed. First exception on row 1; first error: INVALID_FIELD, Cannot specify both an external ID reference Parent and a salesforce id, ParentId: []" 

Here our Parent is Account and ParentId is AccountId these can be different depending upon your field name and what objects you're dealing with. This simply means:

"Whenever we're performing a DML on child records, we cannot specify both the parent reference record, as well as the parent record id together, even if the parent record id is null"

So, make sure you're only specifying one of these. That's all for this tutorial everyone, I hope you liked it. Let me know your feedback in the comments down below.

Happy Trailblazing!!

Saturday, 2 October 2021

First error: Exceeded max size limit of 6000000 OR Exceeded max size limit of 12000000 - Solution

Hello Trailblazers, in this post we're going to talk about the resolution of a very common error as given below:

First error: Exceeded max size limit of 12000000 OR First error: Exceeded max size limit of 6000000 

This error appears when you've exceeded the heap size limit in a single transaction. The heap size limit for a synchronous transaction is 6MB and for an asynchronous transaction is 12MB. Asynchronous transactions basically consists of batch classes, future methods and queueable apex.

How to resolve this error?

You need to reduce the heap size in order to resolve this error. Heap size is basically the memory that your code is taking up while executing. It is the sum of all the memory your variables, maps, sets, lists or any other data structure is consuming. In order to resolve the error you need to reduce the heap size. Below are some tips to help you out:

1. Use Limits.getHeapSize() to understand how much heap size (approx) is used in the current transaction upto a particular point.

2. Use Limits.getLimitHeapSize() to get the total amout of heap size available in the current transaction.

3. Use SOQL For Loops, if you use SOQL for loops the result of the query is given in the batch of 200 records while using a list variable or a single record is returned while using a single instance variable. In both the cases, lesser memory is consumed as we're only storing a maximum 200 records at a time (compared to 50,000 records queried in a single SOQL statement).
List<Account> accounts = [SELECT Id FROM Account];
// Stores 50,000 records at max - greater heap size
for(List<Account> accounts : [SELECT Id FROM Account]) {
}
// Stores 200 records at max - lesser heap size

4. Manage your code effectively using helper functions - Avoid class level variables.
void callMe()
{
    List<Account> accounts = [SELECT Id FROM Account];
    System.debug(Limits.getHeapSize()); // Coming as: 321085
}
callme();
System.debug(Limits.getHeapSize()); // Coming as: 1044

As you can see above, the heap size is reduced after the function call is complete because the call stack is now empty and the local variables are destroyed.

5. Clear the lists/map/set/variables which are not in use or make them null



These are some of the tips that can be used to prevent the heap size limit error. Make sure to write effective code when working with large data. If you have any other suggestion, do comment it down below and I'll include that in the blog post. 

That's all for this tutorial everyone I hope you liked it, the full code used in this post can be found here. Do let me know your feedback/thoughts in the comments down below.

Sharing a Salesforce Help Article related to this error: https://help.salesforce.com/s/articleView?id=000321537&type=1

Happy Trailblazing!!

Friday, 14 May 2021

How to solve UNABLE_TO_LOCK_ROW, unable to obtain exclusive access to this record or N records?

Hello Trailblazers,


Recently, I faced this issue in one of the projects I was working on. The issue was UNABLE_TO_LOCK_ROW, unable to obtain exclusive access to this record or N records, here N is just a placeholder in "N records" it can be 10, 20, 30 or even 100...200 for you. Let's have a look at the use case first and then we'll discuss the solution.

Use Case

I was having two objects connected with master detail relationship. So, you can consider it like: Object A which is the parent object and Object B which is the child object.

Object B (Child) -> Object A (Parent)

To simplify the terminology here, we're going to consider that our custom Object B is "Product Parts" and our custom Object A is "Product". So, each product part must be linked to a single product and one product can have multiple parts. So, our relationship is simplified as:

Product Part (Child) -> Product (Parent)

Let's consider that we have a field named as: Product__c on the Product Part object which is a master detail relationship between product and product parts.

Now, there were around 1.5 million records of Product Part and as we have a master detail relationship, so, it was sure that each Product Part record is linked to a record of Product. In our use case, we're updating the count of each product part on a daily basis using a batch class.

So, you can consider that 1.5 million records are updated on a daily basis. Even if we consider the maximum batch size of 2000, there will be around 750 batches that are running on a daily basis to update all records.

This is how the start method of my batch class looked like:

As you can see above, I am simply returning all the records of my Product_Part__c object as I need to update the count of all of them. Let's have a quick look at execute method as well.

As you can see above, I am simply updating the count of all the product parts to 100. There was nothing in my finish() method, so, I am not specifying that here. It was an empty method.

So, let's consider that for about 1.5M product parts, we're having around 15,000 products to which these product parts are linked to. When I ran my batch class over these records, I got the below error:

Developer script exception from SFDC Stop : 'ProductPartCountUpdateBatch' for job id '1024K0000OVBnlO' : Update failed. First exception on row 0 with id o1g4N00002wG5R4MBT; first error: UNABLE_TO_LOCK_ROW, unable to obtain exclusive access to this record or 100 records: 025A4m00003uCJs5QBL,025LO00002eCVhoAGH,.....

So, my batch class failed, I wasn't able to find any issue in the code, so, let's learn why it failed first of all.

Why it failed?

I noticed that all those ids that I got in my error message were not really the id of my Product Part records, They were the ids of my Product records. Interesting right?

After proceeding with my investigation, I read this in Salesforce documentationIf a record on the master side of a master-detail relationship has too many child records (thousands) you are likely to encounter these errors as well, as every time you edit the detail record, the master record is locked. 

So, now we know that our batch was failing because I was querying the records in the random order and multiple batches were trying to lock the same parent (product) record again and again. Let's have a look at the resolution now.

How to resolve it?

To resolve this issue, I just ordered my query in the start method as shown below:

As you can see above, I just ordered my product part records by using the Product__c which is nothing but the master detail field connecting product and product parts. Due to this small change, my product part records were clubbed together that belong to the same Product and were processed together as well.

There was no record locking issue after that as all the product part records which are linked to the same product were processed together and the same product record was not locked again and again by different child records in different batches.

Easy! right?

So, next time you face this UNABLE_TO_LOCK_ROW issue make sure to have a look at your object relationships and try to minimize the possibilities of error by making small enhancements in your query/code. Here we resolved a big issue by just Ordering the records together!

Happy Trailblazing..!!