Hello Trailblazers,
In this post we're going to learn about Dynamic Apex and the most common use cases that we can solve using it. Let's begin.
Tutorial Video
What is Dynamic Apex?
As per the Salesforce Documentation, Dynamic Apex enables developers to create more flexible applications. In other words, we can say that using dynamic apex, you can write Generic Code that can be re-used again and again with multiple sObjects. So, the logic that you've written is not object dependent. You can make your SOQL queries, SOSL queries and DML statements dynamic. Let's have a look at some good use cases to see how that works.
Get all object API names with their labels
Sometimes, you need to get a list of all sObjects that are present in your org. Let's have a look at the sObject convertor screenshot to understand the use case:
As you can see, here we need to select the Source and Destination sObject to convert the records of source sObject to destination sObject. Therefore, we need to display the API names or Labels of all the sObjects that are present in the salesforce org. Let's see how we can do that using dynamic apex:
// * Initializing Map Map<String, String> objectAPINameToLabelMap = new Map<String, String>(); // * Getting all objects metadata Map<String, Schema.SObjectType> globalDescribeMap = Schema.getGlobalDescribe(); // * Processing each sObject one by one for(String globalDescribeKey : globalDescribeMap.keySet()) { // * Getting the current sObject type Schema.SObjectType currentSObjectType = globalDescribeMap.get(globalDescribeKey); // * Getting the current sObject description result from sObject Type Schema.DescribeSObjectResult currentSObjectResult = currentSObjectType.getDescribe(); // * Getting the API name and value of current sObject and adding it to the map objectAPINameToLabelMap.put(currentSObjectResult.getName(), currentSObjectResult.getLabel()); } // * Processing each entry of the map one by one and displaying sObject API Name and Label for(String objectAPIName : objectAPINameToLabelMap.keySet()) { System.debug(objectAPIName + ' --> ' + objectAPINameToLabelMap.get(objectAPIName)); }
As you can see in the above code snippet, first of all we're initializing a map of string,string named objectAPINameToLabelMap. Then, we're getting all sObjects metadata from salesforce using getGlobalDescribe() method of Schema class. This method will return a map of sObject name as key and an object of SObjectType class as value. The SObjectType class is defined in Schema namespace.
Once, we've a map of object names with their sObject types, we loop each entry in the map and assign each sObject type to the currentSObjectType variable. From the currentSObjectType, we're using the getDescribe() method to get it's metadata which is an object of DescribeSObjectResult class defined in Schema namespace. DescribeSObjectResult consist of all the metadata for current sObject. Once, we have that, we can get the current sobject api name and label using the getName() and getLabel() methods and store them in objectAPINameToLabelMap.
Important Note: The Schema in Schema.SObjectType and Schema.DescribeSObjectResult is a namespace whereas, the Schema in Schema.getGlobalDescribe() is a class. Please don't get confused between the two.
Finally, we're displaying the key and value of each entry in this map using a for loop. The key is the API name of the object and the value is the object label. The output of this code is given below:
As you can see in the above output, some of the internal objects may not have a label as well. For example, AppointmentTopicTimeSlotFeed is not having a label and we're getting the text: "__MISSING LABEL__ PropertyFile - val AppointmentTopicTimeSlot not found in section StandardFeedLabel" from the getLabel() method. It's a high possibility that such objects are internal and you don't need to use these objects in your logic.
Checking object permissions for the logged in user
We can also get the sObjectType by using the getSObjectType() method directly on the object api name. Let's consider the below code:
Schema.SObjectType currentObjectType = AppointmentTopicTimeSlotFeed.getSObjectType(); Schema.DescribeSObjectResult currentSObjectResult = currentObjectType.getDescribe(); System.debug('API Name = ' + currentSObjectResult.getName()); System.debug('Is Custom Object = ' + currentSObjectResult.isCustom()); System.debug('Is Accessible = ' + currentSObjectResult.isAccessible()); System.debug('Is Creatable = ' + currentSObjectResult.isCreateable()); System.debug('Is Updateable = ' + currentSObjectResult.isUpdateable()); System.debug('Is Deletable = ' + currentSObjectResult.isDeletable());
As you can see above, first of all we're getting the SObjectType by using the getSObjectType() method on AppointmentTopicTimeSlotFeed object. Then, we're getting it's DescribeSObjectResult by using the getDescribe() method as we have done before. Then, we're displaying some properties of the object like:
- Object API Name
- Is the object a custom object?
- Is the object accessible?
- Is the object creatable?
- Is the object updateable?
- Is the object deletable?
The answer to these questions are really necessary when you want to check the object permissions for the current logged in user before perfoming an operation like: SOQL, DML etc. Let's have a look at the output below:
As you can see, the object is not a custom object, it's records are accessible and deletable by the user, but the user cannot create or update the records. Depending upon these properties, we decide whether to consider this object in our generic code for business logic or not.
Get all the fields for an object
Sometimes it's required to get a list of fields for a standard/custom sobject. We used the same in sObject Convertor as well because after selecting the objects, we need to define field mapping.
Let's say in our case, we want to get all the fields for Account sObject. The below code snippet will help to get it:
// * Initializing set of field names Set<String> accountFieldNames = new Set<String>(); // * Getting the account sObject Type object Schema.SObjectType accountObjectType = Account.getSObjectType(); // * Getting the sObject describe result for account object (metadata) Schema.DescribeSObjectResult accountSObjectResult = accountObjectType.getDescribe(); // * Getting the map of account fields Map<String, Schema.SObjectField> accountFieldsMap = accountSObjectResult.fields.getMap(); // * Processing each account field one by one for(String accountFieldKey : accountFieldsMap.keySet()) { // * Getting the current sobject field Schema.SObjectField accountField = accountFieldsMap.get(accountFieldKey); // * Getting the current sobject field description (field metadata) from sobject field Schema.DescribeFieldResult accountFieldResult = accountField.getDescribe(); // * Extracting the api name of the field from field description and adding it to the account field names set accountFieldNames.add(accountFieldResult.getName()); } // * Processing each entry of the account field names one by one and displaying them for(String accountFieldName : accountFieldNames) { System.debug(accountFieldName); }
As you can see above, first of all we've initialized a set of string accountFieldNames. Then we're getting sobject type of account and storing it in accountObjectType variable. We're also storing describe result of account in accountSObjectResult. Once, we have the account metadata, it's time to get the field metadata now. For that, we've used accountSObjectResult.fields.getMap() to get the fields map for account. (fields is also defined as a method in DescribeSObjectResult class whose return value is a special data type. It should either be followed by a field API name like: fields.Name or it should be followed by getMap() method). Then, we're iterating the field map using a for loop, getting each field one by one and storing it in accountField variable which is of type Schema.SObjectField. This is similar as we used SObjectType in case of object.
Now, we have the field reference, we'll extract the metadata from field by using getDescribe() method on the accountField variable. It will return an instance of Schema.DescribeFieldResult which we're storing in accountFieldResult variable. Now, we can use different methods of DescribeFieldResult class to get different metadata about the field. Finally, we're retrieving the API name of each field using the getName() method and storing it in accountFieldNames set.
At last, we're iterating the set and displaying the field names. The output when this code is executed is given below:
If you notice, the above code is specific to account, we can also make it a little more generic by using getGlobalDescribe() method that we learned in the previous section. Have a look at the code below:/* * Description: This method is used to return all accessible fields for an sObject */ public static Set<String> getSObjectFields(String sObjectName) { // * Initializing fieldNames set Set<String> fieldNames = new Set<String>(); // * Getting metadata of all sObjects Map<String, Schema.SObjectType> sObjectMap = Schema.getGlobalDescribe(); // * Getting the reference to current sObject Schema.SObjectType sObjectTypeInstance = sObjectMap.get(sObjectName); if(sObjectTypeInstance!=null) { // * Getting Fields for current sObject Map<String, Schema.SObjectField> fieldMap = sObjectTypeInstance.getDescribe().fields.getMap(); // * Checking each field one by one, if it's accessible, adding it's name to fieldNames set for(Schema.SObjectField field: fieldMap.values()) { Schema.DescribeFieldResult fieldResult = field.getDescribe(); if(fieldResult.isAccessible()) { fieldNames.add(fieldResult.getName()); } } } // * Returning the fieldNames set return fieldNames; } Set<String> fieldNames = getSObjectFields('Account'); for(String fieldName : fieldNames) { System.debug(fieldName); }
In the above code, we've defined a method named getSObjectFields() which will return a set of all field names for a particular sobject whose name is passed in the parameter. Notice, how we've used getGlobalDescribe() to get the sObjectMap and then used the sObjectName variable to get the sObjectType. We've also reduced the code by iterating over fieldMap.values() to get a reference to SObjectField directly instead of getting it from map. We're adding the field name to the fieldNames set only if the current field is accessible so that they can be queried later. The result for account fields will be the same as shown below:We modified the code a little to call this method for Employee__c which is a custom object defined in salesforce org.
Set<String> fieldNames = getSObjectFields('Employee__c'); for(String fieldName : fieldNames) { System.debug(fieldName); }
The result of the modified code is given below:As you can see, we're getting the fields for Employee__c object now. This is the same employee object that we used in Find the Lead Manager Tutorial. You have a method now which will return the fields for an object. This method itself can be used to form a dynamic SOQL when you want to query all the fields for an object. Let's see how!
Dynamic SOQL
We're going to create a method which will return a SOQL query for any object consisting of all the fields that are accessible. Let's see the code quickly:
/* * Description: This method is used to return SOQL query consisting of * all fields for an object that are accessible by the current user */ String getSOQL(String objectName) { // * Getting the field names using the object name Set<String> fieldNames = getSObjectFields(objectName); // * Forming the SOQL query String query = 'SELECT '; for(String fieldName : fieldNames) { query += fieldName + ', '; } // * Removing last , from the SOQL query string query = query.substring(0, query.lastIndexof(',')); // * Adding the object name to the SOQL query += ' FROM ' + objectName; // * Returning the SOQL return query; } // * SOQL query to get all fields for contact String query = getSOQL('Contact'); System.debug(query);
In the above code we have defined a method named getSOQL which will accept the object name in the input and will return the SOQL query string in the output with all accessible fields included in the query. Notice that we're using getSObjectFields() method defined in the previous section to get the object fields as a set of strings. We're then iterating the set to form SOQL query.
We tried the same method to form a query for Contact object and the result when this code is executed is given below:
See, How easy it is now to form a query for any sObject with all fields by using Dynamic Apex. In case you want to actually query the records, you can do that as well by passing this query in Database.query() method as shown below where we've queried Employee__c records:
This is an implementation of Dynamic SOQL. As we're creating the SOQL query dynamically by adding all the fields and using that.
This is an implementation of Dynamic SOQL. As we're creating the SOQL query dynamically by adding all the fields and using that.
Note: You can also check for each field access for the current logged in user like: createable, updateable etc. in the same way like we did for the object.
Get picklist field values
By now, you know how do we get a describe field result using dynamic apex right? The DescribeFieldResult class is having a method named as: getPicklistValues() with a return type as List<Schema.PicklistEntry> which will return the picklist values for the mentioned field. In case the field is not a picklist, this method will give a runtime error. Now the question is - How can we identify if the field is picklist or not?
We have a method named: getType() in the DescribeFieldResult class which will return an enum specifying the field type. Using this, we can check if the field is a picklist or not. Let's have a look at the below code snippet where I have defined a method which will return the picklist values of a field, given the field name and object API name as parameters:
/* * Description: This method is going to return the picklist field values and the associated label * for an object and a field which are passed in as parameters */ Map<String, String> getPicklistValuesMap(String objectAPIName, String fieldAPIName) { // * Initializing picklist field map to story value and label of picklist entries Map<String, String> picklistFieldMap = new Map<String, String>(); // * Getting the field result for the current field Schema.DescribeFieldResult fieldResult = Schema.getGlobalDescribe().get(objectAPIName).getDescribe().fields.getMap().get(fieldAPIName).getDescribe(); // * Checking if the field type is a picklist if(fieldResult.getType() == Schema.DisplayType.Picklist) { // * Getting all picklist entries List<Schema.PicklistEntry> picklistEntries = fieldResult.getPicklistValues(); // * Looping over all picklist entries one by one for(Schema.PicklistEntry picklistEntry : picklistEntries) { // * If the picklist entry is active, getting the label and value and putting those in map if(picklistEntry.isActive()) { String picklistLabel = picklistEntry.getLabel(); String picklistValue = picklistEntry.getValue(); picklistFieldMap.put(picklistValue, picklistLabel); } } } // * Returning the picklist field map return picklistFieldMap; } System.debug('Picklist values for Account Type:'); Map<String, String> picklistValuesMap = getPicklistValuesMap('Account', 'Type'); for(String picklistValue : picklistValuesMap.keySet()) { System.debug('Label = ' + picklistValuesMap.get(picklistValue) + ', Value = ' + picklistValue); }
We have defined a method named: getPicklistValuesMap() above which is going to accept two parameters: objectAPIName and fieldAPIName. This method will return the picklistFieldMap where we'll have picklist value as the key and picklist label as the value in the map. Inside the method, we're getting the describe field result for the current field using the methods discussed before and storing it in fieldResult variable. Once we have it, we're going to check the type of field result to ensure the current field is a picklist by using the getType() method which will return a DisplayType enum value.
If the current field is a picklist, we're getting the values of it using getPicklistValues() which will return a List<Schema.PicklistEntry>. Each PicklistEntry will have properties like: label, value, isActive, isDefault. We're only going to store label and value of picklist entries which are active, inside the map. Finally, we're going to return the map at the end. Note that the picklist value is the key inside the map and the picklist label is the value inside the map. You can reverse it as well depending upon the use case.
In order to test our method, we've tried to get the Type picklist values from the Account object and displayed those by iterating over the map using a loop. The output for the same is given below:
We're able to get all picklist values and their label by just passing the object api name and the field api name as a parameter to the method, interesting right? All thanks to Dynamic Apex.Getting the Record Type Id by using Record Type Name
We created 3 account record types in salesforce org as shown below:
Now, we're going to create a method which will provide us the record type names and their ids in a map. Let's have a look at the code below:
/* * Description: This method is going to return a map of record type name with it's id */ Map<String, Id> getRecordTypeIdsByName(String objectAPIName) { // * Initializing map Map<String, Id> recordTypesMap = new Map<String, Id>(); // * Getting the object result Schema.DescribeSObjectResult objectResult = Schema.getGlobalDescribe().get(objectAPIName)?.getDescribe(); if(objectResult!=null) { // * Getting the record type infos list List<Schema.RecordTypeInfo> recordTypeInfos = objectResult.getRecordTypeInfos(); // * Processing each record type one by one for(Schema.RecordTypeInfo recordTypeInfo : recordTypeInfos) { // * If the current record type is active and avaialable to the logged in user, adding it's name and id to the map if(recordTypeInfo.isActive() && recordTypeInfo.isAvailable()) { recordTypesMap.put(recordTypeInfo.getName(), recordTypeInfo.getRecordTypeId()); } } } // * Returning the record types map return recordTypesMap; } // * Displaying reocrd type map for account object System.debug(getRecordTypeIdsByName('Account'));
As you can see above, we have defined a method named getRecordTypeIdsByName which will accept objectAPIName in the parameter and will return recordTypesMap that contains the record type names and their ids. Inside the method, we're getting the describe object result using the object api name, if that's found, we're going to get the record types list for that object which will be List<Schema.RecordTypeInfo>. In this list, each entry will be an instance of RecordTypeInfo class. The RecordTypeInfo class has various methods that can be used to get the information about the current record type like: getDeveloperName(), getName(), isActive(), isAvailable() etc. For each recordTypeInfo record, we're first of all checking if it's Active and Available to the current logged in user. If yes, then we're adding that record type name with respect to it's id in the recordTypesMap.
We're testing this method by getting all record types for account object. Let's see the output below:
We're getting the correct output i.e. the label of the record type with it's id.Note: If you want to use the developer name of the record type, you can use getDeveloperName() method instead of getName().
Dynamic SOSL
You can also use Dynamic Apex to create SOSL. The procedure is same like we did for SOQL. Have a look at the below screenshots from sObject Convertor where we used SOSL to find the records based on the source object selected in the component and the search text entered by the user.
Wrapping it Up
So, that's all for this tutorial everyone, I hope you liked it. I have created a helper class using all the methods that we discussed in this tutorial. You can access the class here and can use it in your projects rightaway. Let me kow what other use case you solved using Dynamic Apex in the comments down below.
Happy Trailblazing..!!
No comments:
Post a Comment