In my last post, I talked about Dynamic SOQL which lets you build and execute a query ‘on the fly’ or at runtime. Dynamic SOQL is one feature in a family of features collectively called Dynamic Apex. Dynamic Apex lets you build flexible applications where the database objects (or sObjects in Apex terminology) that need to be acted upon are not known at compile time. Examples of such applications are generic report writing tools, graphical query editors etc.. Such tools will have to work in any org where it gets installed, so it is impossible to know ahead of time what sObjects would be present and how the user may wish to manipulate them. Also, such programs should continue working even if new sObjects are added or old ones removed without requiring recompilation. In general you can use these features to avoid harcoding details of the underlying data model.
The first step in any program that lets you manipulate sObjects at runtime is to discover the sObjects present in an org and the related metadata about that object – the fields, access permissions, relationships to other objects etc. The Apex Describe Information lets you do that. It is similar to the capabilities provided by the Partner WSDL API. The partner API is SOAP based and lets you build applications outside the Force.com platform. The Apex describe feature offers similar capabilities to build and execute programs on the Force.com platform.
A Code Example
We will extend the example we built earlier which demonstrated dynamic SOQL. Though the query itself was constructed at runtime, the sObjects that the user could select and the filtering criteria was hardcoded. We will now make this dynamic as well by discovering the sObjects that exist in an org and presenting this list to the user. When the user selects an sObject, the associated fields are presented – the user can now select any field as the filter criteria. The modified page is shown in Figure 1, where the SelectList for the object and the fields (based on the object selected) is automatically populated. The steps involved are as follows…
- Discover all the sObjects in the org and build a list of object names
- Present this list to the user.
- When the user selects a particular sObject, retrieve the fields associated with this object and let the user select the field for the filter criteria along with a value.
- Collect the names of the sObject, the field name and the value entered and use it to construct a dynamic SOQL query and execute it.
- Present the result of the query (the retrieved Ids) back to the user.
Let’s get started – we will focus on the Describe features. The code for the VisualForce page and controller is pretty straightforward and is presented later.
Discovering the objects and the associated metadata
The first step is to discover all the SObjects in an org. We do this by calling getGlobalDescribe() on Schema.
Map<String,Schema.SObjectType> globalObjectMap = Schema.getGlobalDescribe() ;
This generates a Map of all SObjects names in the org (subject to permissions) to a corresponding entity called a token. The token is a serializable handle to the actual sObject. We can use this token to get additional details on the sObject. Working with these tokens and selectively instantiating the subset of the objects that we will actually be working with is much more efficient than instantiating every SObject in the org. The snippet below shows how to start with a Map of all the sObjects, select one of interest (in this case Account) and then retrieve its associated fields and other properties.
Map<String, Schema.SObjectType> m = Schema.getGlobalDescribe() ; Schema.SObjectType s = m.get('Account') ; Schema.DescribeSObjectResult r = s.getDescribe() ; // we can get various attributes and propeties on the SObject System.debug('The label for this Object is '+r.getLabel() ) ; if(r.isCustom()) System.debug(' Custom object' ) ; // etc.. // Get all the fields Map<String, Schema.SObjectField> fields = r.fields.getMap() ; // Can now retrieve a particular field and get additional properties Schema.SObjectField f = fields.get('BillingCity' ) ; Schema.DescribeFieldResult r2 = f.getDescribe() ; System.debug( 'The label for this field is '+r2.getLabel() ) ; // etc....
The getDescribe() method on a SObjectType token returns an object of type DescribeSObjectResult which we can use to retrieve the actual metadata for the object – the attributes, the fields, the relationships etc. The snippet also shows how the fields for a particulat SObject is retrieved, you will notice that a similar pattern is followed. A Map which maps all the field names to their corresponding tokens is returned. A particular field can then be retrieved and the getDescribe call returns an object of type DescribeFieldResult which can then be used to retrieve the metadata on the field – its type, accessibility etc. All these details are described in the docs.
The code for our example is in the attached zip file where we have wrapped the calls in a utility class called MetaInfo. To keep the UI simple, we only allow users to filter on String type fields. The two methods – getObjectNameList() and getStringFieldsNameList() return back a list of all SObject names and the names of all String fields (for a particular SObject) respectively. These methods are called by the custom controller that uses it to populate the SelectList on the UI.
The VisualForce page and controller
The code for the page and the controller is also in the attached zip file. There is no new concept demonstrated here. The controller builds the SelectList’s by calling the appropriate methods on the MetaInfo object. The query method builds and execute a dynamic query based on the user selection.
Running the code
Figure 2 shows the result of running this code with some sample data.
- You can easily move from a DescribeResult (either for a SObject or a field) to its token and vice-versa. Token as explained earlier is just a serialized handle to the actual object so is much more lightweight than instantiating the actual object. Go to the actual object representation only if you need to drill down further, otherwise use the tokens since it is more efficient.
- If you are intending to distribute your code that contains these dynamic features in a managed pacakge, read ‘About API and Dynamic Apex Access in Managed Packages’ in the online help.
- Use the dynamic features only if you need the additional flexibility – there is usually more work involved both in terms of coding and error checking. It can also make the code a little harder to read. Also, allowing users to enter dynamic SOQL strings may cause security concerns, please refer to the documentation on how to address it.
Finally, if you haven’t signed up for Winter ’09 do so now – these dynamic features are fully supported in that release.