Apex Flashcards
Apex is
Hosted—Apex is saved, compiled, and executed on the server—the Lightning Platform.
Object oriented—Apex supports classes, interfaces, and inheritance.
Strongly typed—Apex validates references to objects at compile time.
Multitenant aware—Because Apex runs in a multitenant platform, it guards closely against runaway code by enforcing limits, which prevent code from monopolizing shared resources.
Integrated with the database—It is straightforward to access and manipulate records. Apex provides direct access to records and their fields, and provides statements and query languages to manipulate those records.
Data focused—Apex provides transactional access to the database, allowing you to roll back operations.
Easy to use—Apex is based on familiar Java idioms.
Easy to test—Apex provides built-in support for unit test creation, execution, and code coverage. Salesforce ensures that all custom Apex code works as expected by executing all unit tests prior to any platform upgrades.
Versioned—Custom Apex code can be saved against different versions of the API.
Unlike other object-oriented programming languages, Apex supports
Unlike other object-oriented programming languages, Apex supports:
- Cloud development as Apex is stored, compiled, and executed in the cloud.
- Triggers, which are similar to triggers in database systems.
- Database statements that allow you to make direct database calls and query languages to query and search data.
- Transactions and rollbacks.
- The global access modifier, which is more permissive than the public modifier and allows access across namespaces and applications.
- Versioning of custom code.
- In addition, Apex is a case-insensitive language.
Choose a Salesforce Org for Apex Development
You can develop Apex in a sandbox, scratch org, or Developer Edition org, but not directly in a production org. With so many choices, here’s some help to determine which org type is right for you and how to create it.
Sandboxes (Recommended)
A sandbox is a copy of your production org’s metadata in a separate environment, with varying amounts of data depending on the sandbox type. A sandbox provides a safe space for developers and admins to experiment with new features and validate changes before deploying code to production. Developer and Developer Pro sandboxes with source tracking enabled can take advantage of many of the features of our Salesforce DX source-driven development tools, including Salesforce CLI, Code Builder, and DevOps Center.
Scratch Orgs (Recommended)
A scratch org is a source-driven and temporary deployment of Salesforce code and metadata. A scratch org is fully configurable, allowing you to emulate different Salesforce editions with different features and settings. Scratch orgs have a maximum 30-day lifespan, with the default set at 7 days.
Developer Edition (DE) Orgs
A DE org is a free org that provides access to many of the features available in an Enterprise Edition org. Developer Edition orgs can become out-of-date over time and have limited storage. Developer Edition orgs don’t have source tracking enabled and can’t be used as development environments in DevOps Center. Developer Edition orgs expire if they aren’t logged into regularly. You can sign up for as many Developer Edition orgs as you like on the Developer Edition Signup page.
Trial Edition Orgs
Trial editions usually expire after 30 days, so they’re great for evaluating Salesforce functionality but aren’t intended for use as a permanent development environment. Although Apex triggers are available in trial editions, they’re disabled when you convert to any other edition. Deploy your code to another org before conversion to retain your Apex triggers. Salesforce offers several product- and industry-specific free trial orgs.
Production Orgs (Not Supported)
A production org is the final destination for your code and applications, and has live users accessing your data. You can’t develop Apex in your Salesforce production org, and we recommend that you avoid directly modifying any code or metadata directly in production. Live users accessing the system while you’re developing can destabilize your data or corrupt your application.
Developer Console
The Developer Console is an integrated development environment (IDE) built into Salesforce. Use it to create, debug, and test Apex classes and triggers.
To open the Developer Console from Lightning Experience: Click the quick access menu (Gear icon in upper right of Salesforce org), then click Developer Console.
To open the Developer Console from Salesforce Classic: Click Your Name | Developer Console.
The Developer Console supports these tasks:
- Writing code—You can add code using the source code editor. Also, you can browse packages in your organization.
- Compiling code—When you save a trigger or class, the code is automatically compiled. Any compilation errors are reported.
- Debugging—You can view debug logs and set checkpoints that aid in debugging.
- Testing—You can execute tests of specific test classes or all tests in your organization, and you can view test results. Also, you can inspect code coverage.
- Checking performance—You can inspect debug logs to locate performance bottlenecks.
- SOQL queries—You can query data in your organization and view the results using the Query Editor.
- Color coding and autocomplete—The source code editor uses a color scheme for easier readability of code elements and provides autocompletion for class and method names.
Data Types
In Apex, all variables and expressions have a data type, such as sObject, primitive, or enum.
1. A primitive, such as an Integer, Double, Long, Date, Datetime, String, ID, or Boolean (see Primitive Data Types)
2. An sObject, either as a generic sObject or as a specific sObject, such as an Account, Contact, or MyCustomObject_c (see Working with sObjects in Chapter 4.)
A collection, including:
3. A list (or array) of primitives, sObjects, user defined objects, objects created from Apex classes, or collections (see Lists)
4. A set of primitives (see Sets)
5. A map from a primitive to a primitive, sObject, or collection (see Maps)
6. A typed list of values, also known as an enum (see Enums)
7. Objects created from user-defined Apex classes (see Classes, Objects, and Interfaces)
8. Objects created from system supplied Apex classes
9. Null (for the null constant, which can be assigned to any variable)
Methods can return values of any of the listed types, or return no value and be of type Void.
Type checking is strictly enforced at compile time.
sObject Types
Account a = new Account(); MyCustomObjectc co = new MyCustomObject\_\_c(); // Cast the generic variable s from the example above // into a specific account and account variable a Account a = (Account) s; // The following generates a runtime error Contact c = (Contact) s;
Custom Labels
Custom labels aren’t standard sObjects. You can’t create a new instance of a custom label. You can only access the value of a custom label using system.label.label_name. For example:
String errorMsg = System.Label.genericerror;
The following example shows how you can use SOSL over a set of records to determine their object types. Once you have converted the generic SObject record into a Contact, Lead, or Account, you can modify its fields accordingly
public class convertToCLA { List<Contact> contacts = new List<Contact>(); List<Lead> leads = new List<Lead>(); List<Account> accounts = new List<Account>(); public void convertType(String phoneNumber) { List<List<SObject>> results = [FIND :phoneNumber IN Phone FIELDS RETURNING Contact(Id, Phone, FirstName, LastName), Lead(Id, Phone, FirstName, LastName), Account(Id, Phone, Name)]; List<SObject> records = new List<SObject>(); records.addAll(results[0]); //add Contact results to our results super-set records.addAll(results[1]); //add Lead results records.addAll(results[2]); //add Account results if (!records.isEmpty()) { for (Integer i = 0; i < records.size(); i++) { SObject record = records[i]; if (record.getSObjectType() == Contact.sObjectType) { contacts.add((Contact) record); } else if (record.getSObjectType() == Lead.sObjectType){ leads.add((Lead) record); } else if (record.getSObjectType() == Account.sObjectType) { accounts.add((Account) record); } } } } }
Using SObject Fields
SObject fields can be initially set or not set (unset); unset fields are not the same as null or blank fields. When you perform a DML operation on an SObject, you can change a field that is set; you can’t change unset fields.
Note
To erase the current value of a field, set the field to null.
If an Apex method takes an SObject parameter, you can use the System.isSet() method to identify the set fields. If you want to unset any fields to retain their values, first create an SObject instance. Then apply only the fields you want to be part of the DML operation.
An expression with SObject fields of type Boolean evaluates to true only if the SObject field is true. If the field is false or null, the expression evaluates to false.
This example code shows an expression that checks if the IsActive field of a Campaign object is null. Because this expression always evaluates to false, the code in the if statement is never executed.
Campaign cObj= new Campaign(); ... if (cObj.IsActive == null) { ... // IsActive is evaluated to false and this code block is not executed. }
Single vs. Bulk DML Operations
You can perform DML operations either on a single sObject, or in bulk on a list of sObjects. Performing bulk DML operations is the recommended way because it helps avoid hitting governor limits, such as the DML limit of 150 statements per Apex transaction.
Another DML governor limit is the total number of rows that can be processed by DML operations in a single transaction, which is 10,000. All rows processed by all DML calls in the same transaction count incrementally toward this limit. For example, if you insert 100 contacts and update 50 contacts in the same transaction, your total DML processed rows are 150. You still have 9,850 rows left (10,000 - 150).
System Context and Sharing Rules
Most DML operations execute in system context, ignoring the current user’s permissions, field-level security, organization-wide defaults, position in the role hierarchy, and sharing rules.
Note
If you execute DML operations within an anonymous block, they execute using the current user’s object and field-level permissions.
DML Operations, except create
Among the operations you can perform are record updates, deletions, restoring records from the Recycle Bin, merging records, or converting leads. After querying for records, you get sObject instances that you can modify and then persist the changes of.
DML Statements vs. Database Class Methods
Apex offers two ways to perform DML operations: using DML statements or Database class methods. This provides flexibility in how you perform data operations. DML statements are more straightforward to use and result in exceptions that you can handle in your code.
One difference between the two options is that by using the Database class method, you can specify whether or not to allow for partial record processing if errors are encountered. You can do so by passing an additional second Boolean parameter. If you specify false for this parameter and if a record fails, the remainder of DML operations can still succeed. Also, instead of exceptions, a result object array (or one result object if only one sObject was passed in) is returned containing the status of each operation and any errors encountered. By default, this optional parameter is true, which means that if at least one sObject can’t be processed, all remaining sObjects won’t and an exception will be thrown for the record that causes a failure.
The following helps you decide when you want to use DML statements or Database class methods.
Use DML statements if you want any error that occurs during bulk DML processing to be thrown as an Apex exception that immediately interrupts control flow (by using try. . .catch blocks). This behavior is similar to the way exceptions are handled in most database procedural languages.
Use Database class methods if you want to allow partial success of a bulk DML operation—if a record fails, the remainder of the DML operation can still succeed. Your application can then inspect the rejected records and possibly retry the operation. When using this form, you can write code that never throws DML exception errors. Instead, your code can use the appropriate results array to judge success or failure. Note that Database methods also include a syntax that supports thrown exceptions, similar to DML statements.
Most operations overlap between the two, except for a few.
The convertLead operation is only available as a Database class method, not as a DML statement.
The Database class also provides methods not available as DML statements, such as methods transaction control and rollback, emptying the Recycle Bin, and methods related to SOQL queries.
DML Operations As Atomic Transactions
DML operations execute within a transaction. All DML operations in a transaction either complete successfully, or if an error occurs in one operation, the entire transaction is rolled back and no data is committed to the database. The boundary of a transaction can be a trigger, a class method, an anonymous block of code, an Apex page, or a custom Web service method.
All operations that occur inside the transaction boundary represent a single unit of operations. This also applies to calls that are made from the transaction boundary to external code, such as classes or triggers that get fired as a result of the code running in the transaction boundary. For example, consider the following chain of operations: a custom Apex Web service method calls a method in a class that performs some DML operations. In this case, all changes are committed to the database only after all operations in the transaction finish executing and don’t cause any errors. If an error occurs in any of the intermediate steps, all database changes are rolled back and the transaction isn’t committed.
DML: Inserting and Updating Records
Account[] accts = new List<Account>(); for(Integer i=0;i<3;i++) { Account a = new Account(Name='Acme' + i, BillingCity='San Francisco'); accts.add(a); } Account accountToUpdate; try { insert accts; // Update account Acme2. accountToUpdate = [SELECT BillingCity FROM Account WHERE Name='Acme2' AND BillingCity='San Francisco' LIMIT 1]; // Update the billing city. accountToUpdate.BillingCity = 'New York'; // Make the update call. update accountToUpdate; } catch(DmlException e) { System.debug('An unexpected error has occurred: ' + e.getMessage()); } // Verify that the billing city was updated to New York. Account afterUpdate = [SELECT BillingCity FROM Account WHERE Id=:accountToUpdate.Id]; System.assertEquals('New York', afterUpdate.BillingCity);
DML: Inserting Related Records
You can insert records related to existing records if a relationship has already been defined between the two objects, such as a lookup or master-detail relationship. A record is associated with a related record through a foreign key ID. For example, when inserting a new contact, you can specify the contact’s related account record by setting the value of the AccountId field.
~~~
try {
Account acct = new Account(Name=’SFDC Account’);
insert acct;
// Once the account is inserted, the sObject will be // populated with an ID. // Get this ID. ID acctID = acct.ID; // Add a contact to this account. Contact con = new Contact( FirstName='Joe', LastName='Smith', Phone='415.555.1212', AccountId=acctID); insert con; } catch(DmlException e) { System.debug('An unexpected error has occurred: ' + e.getMessage()); } ~~~
DML: Updating Related Records
Fields on related records can’t be updated with the same call to the DML operation and require a separate DML call. For example, if inserting a new contact, you can specify the contact’s related account record by setting the value of the AccountId field. However, you can’t change the account’s name without updating the account itself with a separate DML call. Similarly, when updating a contact, if you also want to update the contact’s related account, you must make two DML calls. The following example updates a contact and its related account using two update statements.
DML: Relating Records by Using an External ID
Add related records by using a custom external ID field on the parent record. Associating records through the external ID field is an alternative to using the record ID. You can add a related record to another record only if a relationship (such as master-detail or lookup) has been defined for the objects involved.
This example relates a new opportunity to an existing account. The Account sObject has a custom field marked as External ID. An opportunity record is associated to the account record through the custom External ID field. The example assumes that:
The Account sObject has an external ID field of type text and named MyExtID
An account record exists where MyExtID_c = ‘SAP111111’
Before the new opportunity is inserted, the account record is added to this opportunity as an sObject through the Opportunity.Account relationship field.
~~~
Opportunity newOpportunity = new Opportunity(
Name=’OpportunityWithAccountInsert’,
StageName=’Prospecting’,
CloseDate=Date.today().addDays(7));
// Create the parent record reference.
// An account with external ID = ‘SAP111111’ already exists.
// This sObject is used only for foreign key reference
// and doesn’t contain any other fields.
Account accountReference = new Account(
MyExtID__c=’SAP111111’);
// Add the account sObject to the opportunity.
newOpportunity.Account = accountReference;
// Create the opportunity.
Database.SaveResult results = Database.insert(newOpportunity);
~~~
DML: Creating Parent and Child Records in a Single Statement Using Foreign Keys
You can use external ID fields as foreign keys to create parent and child records of different sObject types in a single step instead of creating the parent record first, querying its ID, and then creating the child record. To do this:
Create the child sObject and populate its required fields, and optionally other fields.
Create the parent reference sObject used only for setting the parent foreign key reference on the child sObject. This sObject has only the external ID field defined and no other fields set.
Set the foreign key field of the child sObject to the parent reference sObject you just created.
Create another parent sObject to be passed to the insert statement. This sObject must have the required fields (and optionally other fields) set in addition to the external ID field.
Call insert by passing it an array of sObjects to create. The parent sObject must precede the child sObject in the array, that is, the array index of the parent must be lower than the child’s index.
You can create related records that are up to 10 levels deep. Also, the related records created in a single call must have different sObject types. For more information, see Creating Records for Different Object Types in the SOAP API Developer Guide.
The following example shows how to create an opportunity with a parent account using the same insert statement. The example creates an Opportunity sObject and populates some of its fields, then creates two Account objects. The first account is only for the foreign key relationship, and the second is for the account creation and has the account fields set. Both accounts have the external ID field, MyExtID_c, set. Next, the sample calls Database.insert by passing it an array of sObjects. The first element in the array is the parent sObject and the second is the opportunity sObject. The Database.insert statement creates the opportunity with its parent account in a single step. Finally, the sample checks the results and writes the IDs of the created records to the debug log, or the first error if record creation fails. This sample requires an external ID text field on Account called MyExtID.
public class ParentChildSample { public static void InsertParentChild() { Date dt = Date.today(); dt = dt.addDays(7); Opportunity newOpportunity = new Opportunity( Name='OpportunityWithAccountInsert', StageName='Prospecting', CloseDate=dt); // Create the parent reference. // Used only for foreign key reference // and doesn't contain any other fields. Account accountReference = new Account( MyExtID\_\_c='SAP111111'); newOpportunity.Account = accountReference; // Create the Account object to insert. // Same as above but has Name field. // Used for the insert. Account parentAccount = new Account( Name='Hallie', MyExtID\_\_c='SAP111111'); // Create the account and the opportunity. Database.SaveResult[] results = Database.insert(new SObject[] { parentAccount, newOpportunity }); // Check results. for (Integer i = 0; i < results.size(); i++) { if (results[i].isSuccess()) { System.debug('Successfully created ID: ' \+ results[i].getId()); } else { System.debug('Error: could not create sobject ' \+ 'for array element ' + i + '.'); System.debug(' The error reported was: ' \+ results[i].getErrors()[0].getMessage() + '\n'); } } } }
DML: Upserting Records
Using the upsert operation, you can either insert or update an existing record in one call. To determine whether a record already exists, the upsert statement or Database method uses the record’s ID as the key to match records, a custom external ID field, or a standard field with the idLookup attribute set to true.
* If the key isn’t matched, then a new object record is created.
* If the key is matched once, then the existing object record is updated.
* If the key is matched multiple times, then an error is generated and the object record is not inserted or updated.
Note
Custom field matching is case-insensitive only if the custom field has the Unique and Treat “ABC” and “abc” as duplicate values (case insensitive) attributes selected as part of the field definition. If this is the case, “ABC123” is matched with “abc123.”
Account[] acctsList = [SELECT Id, Name, BillingCity FROM Account WHERE BillingCity = 'Bombay']; for (Account a : acctsList) { a.BillingCity = 'Mumbai'; } Account newAcct = new Account(Name = 'Acme', BillingCity = 'San Francisco'); acctsList.add(newAcct); try { upsert acctsList; } catch (DmlException e) { // Process exception here }
Use of upsert with an external ID can reduce the number of DML statements in your code, and help you to avoid hitting governor limits (see Execution Governors and Limits).
This example uses upsert and an external ID field Line_Item_Id_c on the Asset object to maintain a one-to-one relationship between an asset and an opportunity line item. Before running the sample, create a custom text field on the Asset object named Line_Item_Id_c and mark it as an external ID.
Note
External ID fields used in upsert calls must be unique or the user must have the View All Data permission.
public void upsertExample() { Opportunity opp = [SELECT Id, Name, AccountId, (SELECT Id, PricebookEntry.Product2Id, PricebookEntry.Name FROM OpportunityLineItems) FROM Opportunity WHERE HasOpportunityLineItem = true LIMIT 1]; Asset[] assets = new Asset[]{}; // Create an asset for each line item on the opportunity for (OpportunityLineItem lineItem:opp.OpportunityLineItems) { //This code populates the line item Id, AccountId, and Product2Id for each asset Asset asset = new Asset(Name = lineItem.PricebookEntry.Name, LineItemIDc = lineItem.Id, AccountId = opp.AccountId, Product2Id = lineItem.PricebookEntry.Product2Id); assets.add(asset); } try { upsert assets LineItemIDc; // This line upserts the assets list with // the LineItemIdc field specified as the // Asset field that should be used for matching // the record that should be upserted. } catch (DmlException e) { System.debug(e.getMessage()); } }
DML: Merging Records
When you have duplicate lead, contact, case, or account records in the database, cleaning up your data and consolidating the records might be a good idea. You can merge up to three records of the same sObject type. The merge operation merges up to three records into one of the records, deletes the others, and reparents any related records.
Merge Considerations
When merging sObject records, consider the following rules and guidelines:
1. Only leads, contacts, cases, and accounts can be merged. See sObjects That Don’t Support DML Operations.
2. You can pass a master record and up to two additional sObject records to a single merge method.
3. Using the Apex merge operation, field values on the master record always supersede the corresponding field values on the records to be merged. To preserve a merged record field value, simply set this field value on the master sObject before performing the merge.
4. External ID fields can’t be used with merge.
For more information on merging leads, contacts and accounts, see the Salesforce online help.
Example
The following shows how to merge an existing Account record into a master account. The account to merge has a related contact, which is moved to the master account record after the merge operation. Also, after merging, the merge record is deleted and only one record remains in the database. This examples starts by creating a list of two accounts and inserts the list. Then it executes queries to get the new account records from the database, and adds a contact to the account to be merged. Next, it merges the two accounts. Finally, it verifies that the contact has been moved to the master account and the second account has been deleted.
// Insert new accounts List<Account> ls = new List<Account>{ new Account(name='Acme Inc.'), new Account(name='Acme') }; insert ls; // Queries to get the inserted accounts Account masterAcct = [SELECT Id, Name FROM Account WHERE Name = 'Acme Inc.' LIMIT 1]; Account mergeAcct = [SELECT Id, Name FROM Account WHERE Name = 'Acme' LIMIT 1]; // Add a contact to the account to be merged Contact c = new Contact(FirstName='Joe',LastName='Merged'); c.AccountId = mergeAcct.Id; insert c; try { merge masterAcct mergeAcct; } catch (DmlException e) { // Process exception System.debug('An unexpected error has occurred: ' + e.getMessage()); } // Once the account is merged with the master account, // the related contact should be moved to the master record. masterAcct = [SELECT Id, Name, (SELECT FirstName,LastName From Contacts) FROM Account WHERE Name = 'Acme Inc.' LIMIT 1]; System.assert(masterAcct.getSObjects('Contacts').size() > 0); System.assertEquals('Joe', masterAcct.getSObjects('Contacts')[0].get('FirstName')); System.assertEquals('Merged', masterAcct.getSObjects('Contacts')[0].get('LastName')); // Verify that the merge record got deleted Account[] result = [SELECT Id, Name FROM Account WHERE Id=:mergeAcct.Id]; System.assertEquals(0, result.size());
DML: Deleting Records
After you persist records in the database, you can delete those records using the delete operation. Deleted records aren’t deleted permanently from Salesforce, but they are placed in the Recycle Bin for 15 days from where they can be restored.
~~~
Account[] doomedAccts = [SELECT Id, Name FROM Account
WHERE Name = ‘DotCom’];
try {
delete doomedAccts;
} catch (DmlException e) {
// Process exception here
}
~~~
DML: Referential Integrity When Deleting and Restoring Records
The delete operation supports cascading deletions. If you delete a parent object, you delete its children automatically, as long as each child record can be deleted.
For example, if you delete a case record, Apex automatically deletes any CaseComment, CaseHistory, and CaseSolution records associated with that case. However, if a particular child record is not deletable or is currently being used, then the delete operation on the parent case record fails.
The undelete operation restores the record associations for the following types of relationships:
1. Parent accounts (as specified in the Parent Account field on an account)
1. Indirect account-contact relationships (as specified on the Related Accounts related list on a contact or the Related Contacts related list on an account)
1. Parent cases (as specified in the Parent Case field on a case)
1. Master solutions for translated solutions (as specified in the Master Solution field on a solution)
1. Managers of contacts (as specified in the Reports To field on a contact)
1. Products related to assets (as specified in the Product field on an asset)
1. Opportunities related to quotes (as specified in the Opportunity field on a quote)
1. All custom lookup relationships
1. Relationship group members on accounts and relationship groups, with some exceptions
1. Tags
1. An article’s categories, publication state, and assignments
DML: Restoring Deleted Records
After you have deleted records, the records are placed in the Recycle Bin for 15 days, after which they are permanently deleted. While the records are still in the Recycle Bin, you can restore them using the undelete operation. If you accidentally deleted some records that you want to keep, restore them from the Recycle Bin.
Account a = new Account(Name='Universal Containers'); insert(a); insert(new Contact(LastName='Carter',AccountId=a.Id)); delete a; Account[] savedAccts = [SELECT Id, Name FROM Account WHERE Name = 'Universal Containers' ALL ROWS]; undelete savedAccts;
Undelete Considerations
1. You can undelete records that were deleted as the result of a merge. However, the merge reparents the child objects, and that reparenting can’t be undone.
2. To identify deleted records, including records deleted as a result of a merge, use the ALL ROWS parameters with a SOQL query.
Database Class: Converting Leads
The convertLead DML operation converts a lead into an account and contact, as well as (optionally) an opportunity. convertLead is available only as a method on the Database class; it is not available as a DML statement.
Converting leads involves the following basic steps:
1. Your application determines the IDs of any lead(s) to be converted.
2. Optionally, your application determines the IDs of any account(s) into which to merge the lead. Your application can use SOQL to search for accounts that match the lead name.
3. Optionally, your application determines the IDs of the contact or contacts into which to merge the lead. The application can use SOQL to search for contacts that match the lead contact name.
4. Optionally, the application determines whether opportunities should be created from the leads.
5. The application uses the query (SELECT … FROM LeadStatus WHERE IsConverted=true) to obtain the leads with converted status.
6. The application calls convertLead.
7. The application iterates through the returned result or results and examines each LeadConvertResult object to determine whether conversion succeeded for each lead.
8. Optionally, when converting leads owned by a queue, the owner must be specified. This is because accounts and contacts can’t be owned by a queue. Even if you are specifying an existing account or contact, you must still specify an owner.
Example
This example shows how to use the Database.convertLead method to convert a lead. It inserts a new lead, creates a LeadConvert object, sets its status to converted, and then passes it to the Database.convertLead method. Finally, it verifies that the conversion was successful.
Lead myLead = new Lead(LastName = ‘Fry’, Company=’Fry And Sons’);
insert myLead;
Database.LeadConvert lc = new database.LeadConvert();
lc.setLeadId(myLead.id);
LeadStatus convertStatus = [SELECT Id, ApiName FROM LeadStatus WHERE IsConverted=true LIMIT 1];
lc.setConvertedStatus(convertStatus.ApiName);
Database.LeadConvertResult lcr = Database.convertLead(lc);
System.assert(lcr.isSuccess());
Convert Leads Considerations
1. Field mappings: The system automatically maps standard lead fields to standard account, contact, and opportunity fields. For custom lead fields, your Salesforce administrator can specify how they map to custom account, contact, and opportunity fields. For more information about field mappings, see Salesforce Help.
2. Merged fields: If data is merged into existing account and contact objects, only empty fields in the target object are overwritten—existing data (including IDs) are not overwritten. The only exception is if you specify setOverwriteLeadSource on the LeadConvert object to true, in which case the LeadSource field in the target contact object is overwritten with the contents of the LeadSource field in the source LeadConvert object.
3. Record types: If the organization uses record types, the default record type of the new owner is assigned to records created during lead conversion. The default record type of the user converting the lead determines the lead source values available during conversion. If the desired lead source values are not available, add the values to the default record type of the user converting the lead. For more information about record types, see Salesforce Help.
4. Picklist values: The system assigns the default picklist values for the account, contact, and opportunity when mapping any standard lead picklist fields that are blank. If your organization uses record types, blank values are replaced with the default picklist values of the new record owner.
5. Automatic feed subscriptions: When you convert a lead into a new account, contact, and opportunity, the lead owner is unsubscribed from the lead record’s Chatter feed. The lead owner, the owner of the generated records, and users that were subscribed to the lead aren’t automatically subscribed to the generated records, unless they have automatic subscriptions enabled in their Chatter feed settings. They must have automatic subscriptions enabled to see changes to the account, contact, and opportunity records in their news feed. To subscribe to records they create, users must enable the Automatically follow records that I create option in their personal settings. A user can subscribe to a record so that changes to the record display in the news feed on the user’s home page. This is a useful way to stay up-to-date with changes to records in Salesforce.
Database Class Method Result Objects
Database class methods return the results of the data operation. These result objects contain useful information about the data operation for each record, such as whether the operation was successful or not, and any error information. Each type of operation returns a specific result object type, as outlined below.
Operation Result Class
insert, update SaveResult Class
upsert UpsertResult Class
merge MergeResult Class
delete DeleteResult Class
undelete UndeleteResult Class
convertLead LeadConvertResult Class
emptyRecycleBin EmptyRecycleBinResult Class
Returned Database Errors
While DML statements always return exceptions when an operation fails for one of the records being processed and the operation is rolled back for all records, Database class methods can either do so or allow partial success for record processing. In the latter case of partial processing, Database class methods don’t throw exceptions. Instead, they return a list of errors for any errors that occurred on failed records.
The errors provide details about the failures and are contained in the result of the Database class method. For example, a SaveResult object is returned for insert and update operations. Like all returned results, SaveResult contains a method called getErrors that returns a list of Database.Error objects, representing the errors encountered, if any.
Setting DML Options
You can specify DML options for insert and update operations by setting the desired options in the Database.DMLOptions object. You can set Database.DMLOptions for the operation by calling the setOptions method on the sObject, or by passing it as a parameter to the Database.insert and Database.update methods.
Using DML options, you can specify:
The truncation behavior of fields.
Assignment rule information.
Duplicate rule information.
Whether automatic emails are sent.
The user locale for labels.
Whether the operation allows for partial success.
The Database.DMLOptions class has the following properties:
allowFieldTruncation Property
assignmentRuleHeader Property
duplicateRuleHeader
emailHeader Property
localeOptions Property
optAllOrNone Property
DMLOptions is only available for Apex saved against API versions 15.0 and higher. DMLOptions settings take effect only for record operations performed using Apex DML and not through the Salesforce user interface.
Database.DMLOptions dmo = new Database.DMLOptions(); dmo.assignmentRuleHeader.useDefaultRule= true; dmo.allowFieldTruncation = true; Lead l = new Lead(company='ABC', lastname='Smith'); l.setOptions(dmo); insert l;
Setting DML Options: Examples
allowFieldTruncation - specifies the truncation behavior of strings. In Apex saved against API versions previous to 15.0, if you specify a value for a string and that value is too large, the value is truncated. For API version 15.0 and later, if a value is specified that is too large, the operation fails and an error message is returned. The allowFieldTruncation property allows you to specify that the previous behavior, truncation, be used instead of the new behavior in Apex saved against API versions 15.0 and later.
assignmentRuleHeader - The assignmentRuleHeader property specifies the assignment rule to be used when creating a case or lead (NOT Account)
duplicateRuleHeader - The duplicateRuleHeader property determines whether a record that’s identified as a duplicate can be saved.
emailHeader - The Salesforce user interface allows you to specify whether or not to send an email when the following events occur:
Creation of a new case or task
Conversion of a case email to a contact
New user email notification
Lead queue email notification
Password reset
In Apex saved against API version 15.0 or later, the Database.DMLOptions emailHeader property enables you to specify additional information regarding the email that gets sent when one of the events occurs because of Apex DML code execution.
Using the emailHeader property, you can set these options.
triggerAutoResponseEmail: Indicates whether to trigger auto-response rules (true) or not (false), for leads and cases. This email can be automatically triggered by a number of events, for example when creating a case or resetting a user password. If this value is set to true, when a case is created, if there is an email address for the contact specified in ContactID, the email is sent to that address. If not, the email is sent to the address specified in SuppliedEmail.
triggerOtherEmail: Indicates whether to trigger email outside the organization (true) or not (false).
triggerUserEmail: Indicates whether to trigger email that is sent to users in the organization (true) or not (false).
localeOptions - The localeOptions property specifies the language of any labels that are returned by Apex. The value must be a valid user locale (language and country), such as de_DE or en_GB. The value is a String, 2-5 characters long. The first two characters are always an ISO language code, for example ‘fr’ or ‘en.’ If the value is further qualified by a country, then the string also has an underscore (_) and another ISO country code, for example ‘US’ or ‘UK.’ For example, the string for the United States is ‘en_US’, and the string for French Canadian is ‘fr_CA’.
optAllOrNone - The optAllOrNone property specifies whether the operation allows for partial success. If optAllOrNone is set to true, all changes are rolled back if any record causes errors. The default for this property is false and successfully processed records are committed while records with errors aren’t. This property is available in Apex saved against Salesforce API version 20.0 and later.
DML: Transaction Control
All requests are delimited by the trigger, class method, Web Service, Visualforce page, or anonymous block that executes the Apex code. If the entire request completes successfully, all changes are committed to the database. For example, suppose a Visualforce page called an Apex controller, which in turn called an additional Apex class. Only when all the Apex code has finished running and the Visualforce page has finished running, are the changes committed to the database. If the request doesn’t complete successfully, all database changes are rolled back.
DML: Generating Savepoints and Rolling Back Transactions
Sometimes during the processing of records, your business rules require that partial work (already executed DML statements) is rolled back so that the processing can continue in another direction. Apex gives you the ability to generate a savepoint, that is, a point in the request that specifies the state of the database at that time. Any DML statement that occurs after the savepoint can be discarded, restoring the database to the condition it was in when you generated the savepoint. All table and row locks acquired since the savepoint are released.
The following limitations apply to generating savepoint variables and rolling back the database:
If you set more than one savepoint, then roll back to a savepoint that isn’t the last savepoint you generated, the later savepoint variable is also rolled back and becomes invalid. For example, if you generated savepoint SP1 first, savepoint SP2 after that, and then you rolled back to SP1, the variable SP2 is no longer valid. If you try to use savepoint SP2, you receive a runtime error.
References to savepoints can’t cross-trigger invocations because each trigger invocation is a new trigger context. If you declare a savepoint as a static variable then try to use it across trigger contexts, you receive a run-time error.
Each savepoint you set counts against the governor limit for DML statements.
Static variables aren’t reverted during a rollback. If you try to run the trigger again, the static variables retain the values from the first run.
Database.rollback(Savepoint) and Database.setSavepoint()don’t count against the DML row limit, but count toward the DML statement limit. This behavior applies to all API versions.
The ID on an sObject inserted after setting a savepoint isn’t cleared after a rollback. Attempting to insert the sObject using the variable created before the rollback fails because the sObject variable has an ID. Updating or upserting the sObject using the same variable also fails because the sObject isn’t in the database and, thus, can’t be updated. To perform further DML operations, create an sObject variable without setting its ID.
The following is an example using the setSavepoint and rollback Database methods.
~~~
Account a = new Account(Name = ‘xyz’);
insert a;
Assert.isNull([SELECT AccountNumber FROM Account WHERE Id = :a.Id]. AccountNumber);
// Create a savepoint while AccountNumber is null
Savepoint sp = Database.setSavepoint();
// Change the account number
a.AccountNumber = ‘123’;
update a;
Assert.areEqual(‘123’, [SELECT AccountNumber FROM Account WHERE Id = :a.Id]. AccountNumber);
// Rollback to the previous null value
Database.rollback(sp);
Assert.isNull([SELECT AccountNumber FROM Account WHERE Id = :a.Id]. AccountNumber);
~~~
DML: Releasing Savepoints and Using Callouts
To allow callouts, roll back all uncommitted DML by using a savepoint. Then use the Database.releaseSavepoint method to explicitly release savepoints before making the desired callout. When Database.releaseSavepoint() is called, SAVEPOINT_RELEASE is logged.
In this example, the makeACallout() callout succeeds because the uncommitted DML is rolled back and the savepoint is released.
Savepoint sp = Database.setSavepoint();
try {
// Try a database operation
insert new Account(name=’Foo’);
integer bang = 1 / 0;
} catch (Exception ex) {
Database.rollback(sp);
Database.releaseSavepoint(sp);
makeACallout();
}
In this example, DML is pending when the callout is made. The CalloutException informs you that you must roll back the transaction before the callout is made or the transaction must be committed.
Savepoint sp = Database.setSavepoint();
insert new Account(name=’Foo’);
Database.releaseSavepoint(sp);
try {
makeACallout();
} catch (System.CalloutException ex) {
Assert.isTrue(ex.getMessage().contains(‘You have uncommitted work pending. Please commit or rollback before calling out.’));
}
Use these guidelines for using callouts and savepoints.
If there’s uncommitted work pending when Database.releaseSavepoint() is called, the uncommitted work isn’t rolled back. It’s committed if the transaction succeeds.
Attempts to roll back to a released savepoint result in a TypeException.
Attempts to roll back after calling Database.releaseSavepoint() result in a System.InvalidOperationException.
Calling the Database.releaseSavepoint() method on a savepoint also releases nested savepoints, that is, any subsequent savepoints created after a savepoint.
For Apex tests with API version 60.0 or later, all savepoints are released when Test.startTest() and Test.stopTest() are called. If any savepoints are reset, a SAVEPOINT_RESET event is logged.
Before API version 60.0, making a callout after creating savepoints throws a CalloutException regardless of whether there was uncommitted DML or the changes were rolled back to a savepoint. Also, before API version 60.0, both Database.rollback(databaseSavepoint) and Database.setSavepoint() calls incremented the DML row usage limit.
sObjects That Can’t Be Used Together in DML Operations
DML operations on certain sObjects, sometimes referred to as setup objects, can’t be mixed with DML on non-setup sObjects in the same transaction. This restriction exists because some sObjects affect the user’s access to records in the org. You must insert or update these types of sObjects in a different transaction to prevent operations from happening with incorrect access-level permissions. For example, you can’t update an account and a user role in a single transaction.
Don’t include more than one of these sObjects in the same transaction when performing DML operations or when using the Metadata API. These sObjects also can’t be used with the @IsTest (IsParellel=true) annotation. Split such operations into separate transactions.
* AuthSession
* FieldPermissions
* ForecastingShare
* Group - You can only insert and update a group in a transaction with other sObjects. Other DML operations aren’t allowed.
* GroupMember
* ObjectPermissions
* ObjectTerritory2AssignmentRule
* ObjectTerritory2AssignmentRuleItem
* PermissionSet
* PermissionSetAssignment
* QueueSObject
* RuleTerritory2Association
* SetupEntityAccess
* Territory
* Territory2
* Territory2Model
* User
If you’re using a Visualforce page with a custom controller, you can’t mix sObject types with any of these special sObjects within a single request or action. However, you can perform DML operations on these different types of sObjects in subsequent requests. For example, you can create an account with a save button, and then create a user with a non-null role with a submit button.
You can perform DML operations on more than one type of sObject in a single class using the following process:
Create a method that performs a DML operation on one type of sObject.
Create a second method that uses the future annotation to manipulate a second sObject type.
This example shows how to perform mixed DML operations by using a future method to perform a DML operation on the User object.
public class MixedDMLFuture { public static void useFutureMethod() { // First DML operation Account a = new Account(Name='Acme'); insert a; // This next operation (insert a user with a role) // can't be mixed with the previous insert unless // it is within a future method. // Call future method to insert a user with a role. Util.insertUserWithRole( 'mruiz@awcomputing.com', 'mruiz', 'mruiz@awcomputing.com', 'Ruiz'); } } public class Util { @future public static void insertUserWithRole( String uname, String al, String em, String lname) { Profile p = [SELECT Id FROM Profile WHERE Name='Standard User']; UserRole r = [SELECT Id FROM UserRole WHERE Name='COO']; // Create new user with a non-null user role ID User u = new User(alias = al, email=em, emailencodingkey='UTF-8', lastname=lname, languagelocalekey='enUS', localesidkey='enUS', profileid = p.Id, userroleid = r.Id, timezonesidkey='America/LosAngeles', username=uname); insert u; } }
Mixed DML Operations in Test Methods
Test methods allow for performing mixed Data Manipulation Language (DML) operations that include both setup sObjects and other sObjects if the code that performs the DML operations is enclosed within System.runAs method blocks. You can also perform DML in an asynchronous job that your test method calls. These techniques enable you, for example, to create a user with a role and other sObjects in the same test.
Example: Mixed DML Operations in System.runAs Blocks
~~~
@isTest
private class MixedDML {
static testMethod void mixedDMLExample() {
User u;
Account a;
User thisUser = [SELECT Id FROM User WHERE Id = :UserInfo.getUserId()];
// Insert account as current user
System.runAs (thisUser) {
Profile p = [SELECT Id FROM Profile WHERE Name=’Standard User’];
UserRole r = [SELECT Id FROM UserRole WHERE Name=’COO’];
u = new User(alias = ‘jsmith’, email=’jsmith@acme.com’,
emailencodingkey=’UTF-8’, lastname=’Smith’,
languagelocalekey=’en_US’,
localesidkey=’en_US’, profileid = p.Id, userroleid = r.Id,
timezonesidkey=’America/Los_Angeles’,
username=’jsmith@acme.com’);
insert u;
a = new Account(name=’Acme’);
insert a;
}
}
}
~~~
Use @future to Bypass the Mixed DML Error in a Test Method
Mixed DML operations within a single transaction aren’t allowed. You can’t perform DML on a setup sObject and another sObject in the same transaction. However, you can perform one type of DML as part of an asynchronous job and the others in other asynchronous jobs or in the original transaction. This class contains an @future method to be called by the class in the subsequent example.
~~~
public class InsertFutureUser {
@future
public static void insertUser() {
Profile p = [SELECT Id FROM Profile WHERE Name=’Standard User’];
UserRole r = [SELECT Id FROM UserRole WHERE Name=’COO’];
User futureUser = new User(firstname = ‘Future’, lastname = ‘User’,
alias = ‘future’, defaultgroupnotificationfrequency = ‘N’,
digestfrequency = ‘N’, email = ‘test@test.org’,
emailencodingkey = ‘UTF-8’, languagelocalekey=’en_US’,
localesidkey=’en_US’, profileid = p.Id,
timezonesidkey = ‘America/Los_Angeles’,
username = ‘futureuser@test.org’,
userpermissionsmarketinguser = false,
userpermissionsofflineuser = false, userroleid = r.Id);
insert(futureUser);
}
}
@isTest
public class UserAndContactTest {
public testmethod static void testUserAndContact() {
InsertFutureUser.insertUser();
Contact currentContact = new Contact(
firstName = String.valueOf(System.currentTimeMillis()),
lastName = ‘Contact’);
insert(currentContact);
}
}
~~~
sObjects That Don’t Support DML Operations
Your organization contains standard objects provided by Salesforce and custom objects that you created. These objects can be accessed in Apex as instances of the sObject data type. You can query these objects and perform DML operations on them. However, some standard objects don’t support DML operations although you can still obtain them in queries. They include the following:
AccountTerritoryAssignmentRule
AccountTerritoryAssignmentRuleItem
ApexComponent
ApexPage
BusinessHours
BusinessProcess
CategoryNode
CurrencyType
DatedConversionRate
NetworkMember (allows update only)
ProcessInstance
Profile
RecordType
SelfServiceUser
StaticResource
Territory2
UserAccountTeamMember
UserPreference
UserTerritory
WebLink
If an Account record has a record type of Person Account, the Name field can’t be modified with DML operations.
Bulk DML Exception Handling
Exceptions that arise from a bulk DML call (including any recursive DML operations in triggers that are fired as a direct result of the call) are handled differently depending on where the original call came from:
1. When errors occur because of a bulk DML call that originates directly from the Apex DML statements, or if the allOrNone parameter of a Database DML method is set to true, the runtime engine follows the “all or nothing” rule: during a single operation, all records must be updated successfully or the entire operation rolls back to the point immediately preceding the DML statement. If the allOrNone parameter of a Database DML method is set to false and a record fails, the remainder of the DML operation can still succeed.
2. You must iterate through the returned results to identify which records succeeded or failed. If the allOrNone parameter of a Database DML method is set to false and a before-trigger assigns an invalid value to a field, the partial set of valid records isn’t inserted.
3. When errors occur because of a bulk DML call that originates from SOAP API with default settings, or if the allOrNone parameter of a Database DML method was specified as false, the runtime engine attempts at least a partial save:
- During the first attempt, the runtime engine processes all records. - Any record that generates an error due to issues such as validation rules or unique index violations is set aside.
- If there were errors during the first attempt, the runtime engine makes a second attempt that includes only those records that didn’t generate errors. All records that didn’t generate an error during the first attempt are processed, and if any record generates an error (perhaps because of race conditions) it’s also set aside.
- If there were additional errors during the second attempt, the runtime engine makes a third and final attempt that includes only those records that didn’t generate errors during the first and second attempts. If any record generates an error, the entire operation fails with the error message, “Too many batch retries in the presence of Apex triggers and partial failures.”
Note
During the second and third attempts, governor limits are reset to their original state before the first attempt.
Apex triggers are fired for the first save attempt, and if errors are encountered for some records and subsequent attempts are made to save the subset of successful records, triggers are refired on this subset of records.
DML: Non-Null Required Fields Values and Null Fields
When inserting new records or updating required fields on existing records, you must supply non-null values for all required fields.
Unlike the SOAP API, Apex allows you to change field values to null without updating the fieldsToNull array on the sObject record. The API requires an update to this array due to the inconsistent handling of null values by many SOAP providers. Because Apex runs solely on the Lightning Platform, this workaround is unnecessary.
DML: String Field Truncation and API Version
Apex classes and triggers saved (compiled) using API version 15.0 and higher produce a runtime error if you assign a String value that is too long for the field.
sObject Properties to Enable DML Operations
To be able to insert, update, delete, or undelete an sObject record, the sObject must have the corresponding property (createable, updateable, deletable, or undeletable respectively) set to true.
DML: ID Values
The insert statement automatically sets the ID value of all new sObject records. Inserting a record that already has an ID—and therefore already exists in your organization’s data—produces an error.
The insert and update statements check each batch of records for duplicate ID values. If there are duplicates, the first five are processed. For the sixth and all additional duplicate IDs, the SaveResult for those entries is marked with an error similar to the following: Maximum number of duplicate updates in one batch (5 allowed). Attempt to update Id more than once in this API call: number_of_attempts.
The ID of an updated sObject record cannot be modified in an update statement, but related record IDs can.
DML: Fields With Unique Constraints
For some sObjects that have fields with unique constraints, inserting duplicate sObject records results in an error. For example, inserting CollaborationGroup sObjects with the same names results in an error because CollaborationGroup records must have unique names.
System Fields Automatically Set
When inserting new records, system fields such as CreatedDate, CreatedById, and SystemModstamp are automatically updated. You cannot explicitly specify these values in your Apex. Similarly, when updating records, system fields such as LastModifiedDate, LastModifiedById, and SystemModstamp are automatically updated.
DML: Maximum Number of Records Processed by DML Statement
You can pass a maximum of 10,000 sObject records to a single insert, update, delete, and undelete method.
Each upsert statement consists of two operations, one for inserting records and one for updating records. Each of these operations is subject to the runtime limits for insert and update, respectively. For example, if you upsert more than 10,000 records and all of them are being updated, you receive an error.
DML: Creating Records for Multiple Object Types
As with the SOAP API, you can create records in Apex for multiple object types, including custom objects, in one DML call with API version 20.0 and later. For example, you can create a contact and an account in one call. You can create records for up to 10 object types in one call.
Records are saved in the same order that they’re entered in the sObject input array. If you’re entering new records that have a parent-child relationship, the parent record must precede the child record in the array. For example, if you’re creating a contact that references an account that’s also being created in the same call, the account must have a smaller index in the array than the contact does. The contact references the account by using an External ID field.
You can’t add a record that references another record of the same object type in the same call. For example, the Contact object has a Reports To field that’s a reference to another contact. You can’t create two contacts in one call if one contact uses the Reports To field to reference a second contact in the input array. You can create a contact that references another contact that has been previously created.
Records for multiple object types are broken into multiple chunks by Salesforce. A chunk is a subset of the input array, and each chunk contains records of one object type. Data is committed on a chunk-by-chunk basis. Any Apex triggers that are related to the records in a chunk are invoked once per chunk. Consider an sObject input array that contains the following set of records:
account1, account2, contact1, contact2, contact3, case1, account3, account4, contact4
Salesforce splits the records into five chunks:
account1, account2
contact1, contact2, contact3
case1
account3, account4
contact4
Each call can process up to 10 chunks. If the sObject array contains more than 10 chunks, you must process the records in more than one call.
Note
For Apex, the chunking of the input array for an insert or update DML operation has two possible causes: the existence of multiple object types or the default chunk size of 200. If chunking in the input array occurs because of both of these reasons, each chunk is counted toward the limit of 10 chunks. If the input array contains only one type of sObject, you won’t hit this limit. However, if the input array contains at least two sObject types and contains a high number of objects that are chunked into groups of 200, you might hit this limit. For example, if you have an array that contains 1,001 consecutive leads followed by 1,001 consecutive contacts, the array will be chunked into 12 groups: Two groups are due to the different sObject types of Lead and Contact, and the remaining are due to the default chunking size of 200 objects. In this case, the insert or update operation returns an error because you reached the limit of 10 chunks in hybrid arrays. The workaround is to call the DML operation for each object type separately.
DML and Knowledge Objects
To execute DML code on knowledge articles (KnowledgeArticleVersion types such as the custom FAQ_kav article type), the running user must have the Knowledge User feature license. Otherwise, calling a class method that contains DML operations on knowledge articles results in errors. If the running user isn’t a system administrator and doesn’t have the Knowledge User feature license, calling any method in the class returns an error even if the called method doesn’t contain DML code for knowledge articles but another method in the class does.
Apex: Locking Statements
In Apex, you can use FOR UPDATE to lock sObject records while they’re being updated in order to prevent race conditions and other thread safety problems.
While an sObject record is locked, no other client or user is allowed to make updates either through code or the Salesforce user interface. The client locking the records can perform logic on the records and make updates with the guarantee that the locked records won’t be changed by another client during the lock period. The lock gets released when the transaction completes.
To lock a set of sObject records in Apex, embed the keywords FOR UPDATE after any inline SOQL statement. For example, the following statement, in addition to querying for two accounts, also locks the accounts that are returned:
Account [] accts = [SELECT Id FROM Account LIMIT 2 FOR UPDATE];
Note
You can’t use the ORDER BY keywords in any SOQL query that uses locking.
Locking Considerations:
1. While the records are locked by a client, the locking client can modify their field values in the database in the same transaction. Other clients have to wait until the transaction completes and the records are no longer locked before being able to update the same records. Other clients can still query the same records while they’re locked.
2. If you attempt to lock a record currently locked by another client, your process waits a maximum of 10 seconds for the lock to be released before acquiring a new lock. If the wait time exceeds 10 seconds, a QueryException is thrown. Similarly, if you attempt to update a record currently locked by another client and the lock isn’t released within a maximum of 10 seconds, a DmlException is thrown.
3. If a client attempts to modify a locked record, the update operation can succeed if the lock gets released within a short amount of time after the update call was made. In this case, it’s possible that the updates overwrite changes made by the locking client if the second client obtained an old copy of the record. To prevent the overwrite from happening, the second client must lock the record first. The locking process returns a fresh copy of the record from the database through the SELECT statement. The second client can use this copy to make new updates.
4. The record locks that are obtained in Apex via FOR UPDATE clause are automatically released when making callouts. The information is logged in the debug log and the logged message includes the most recently locked entity type. For example: FOR_UPDATE_LOCKS_RELEASE FOR UPDATE locks released due to a callout. The most recent lock was Account. Use caution while making callouts in contexts where FOR UPDATE queries could have been previously executed.
5. When you perform a DML operation on one record, related records are locked in addition to the record in question.
Locking in a SOQL For Loop
The FOR UPDATE keywords can also be used within SOQL for loops. For example:
for (Account[] accts : [SELECT Id FROM Account
FOR UPDATE]) {
// Your code
}
As discussed in SOQL For Loops, the example above corresponds internally to calls to the query() and queryMore() methods in the SOAP API.
Note that there is no commit statement. If your Apex trigger completes successfully, any database changes are automatically committed. If your Apex trigger does not complete successfully, any changes made to the database are rolled back.
Apex: Avoiding Deadlocks
Apex has the possibility of deadlocks, as does any other procedural logic language involving updates to multiple database tables or rows. To avoid such deadlocks, the Apex runtime engine:
First locks sObject parent records, then children.
Locks sObject records in order of ID when multiple records of the same type are being edited.
As a developer, use care when locking rows to ensure that you are not introducing deadlocks. Verify that you are using standard deadlock avoidance techniques by accessing tables and rows in the same order from all locations in an application.
Working with SOQL and SOSL Query Results
SOQL and SOSL queries only return data for sObject fields that are selected in the original query. If you try to access a field that was not selected in the SOQL or SOSL query (other than ID), you receive a runtime error, even if the field contains a value in the database.
Even if only one sObject field is selected, a SOQL or SOSL query always returns data as complete records. Consequently, you must dereference the field in order to access it. For example, this code retrieves an sObject list from the database with a SOQL query, accesses the first account record in the list, and then dereferences the record’s AnnualRevenue field:
Double rev = [SELECT AnnualRevenue FROM Account
WHERE Name = ‘Acme’][0].AnnualRevenue;
// When only one result is returned in a SOQL query, it is not necessary to include the list’s index.
Double rev2 = [SELECT AnnualRevenue FROM Account
WHERE Name = ‘Acme’ LIMIT 1].AnnualRevenue;
The only situation in which it is not necessary to dereference an sObject field in the result of an SOQL query, is when the query returns an Integer as the result of a COUNT operation:
Integer i = [SELECT COUNT() FROM Account];
Fields in records returned by SOSL queries must always be dereferenced.
Also note that sObject fields that contain formulas return the value of the field at the time the SOQL or SOSL query was issued. Any changes to other fields that are used within the formula are not reflected in the formula field value until the record has been saved and re-queried in Apex. Like other read-only sObject fields, the values of the formula fields themselves cannot be changed in Apex.
Accessing sObject Fields Through Relationships
sObject records represent relationships to other records with two fields: an ID and an address that points to a representation of the associated sObject. For example, the Contact sObject has both an AccountId field of type ID, and an Account field of type Account that points to the associated sObject record itself.
The ID field can be used to change the account with which the contact is associated, while the sObject reference field can be used to access data from the account. The reference field is only populated as the result of a SOQL or SOSL query (see note).
For example, the following Apex code shows how an account and a contact can be associated with one another, and then how the contact can be used to modify a field on the account:
Account a = new Account(Name = 'Acme'); insert a; // Inserting the record automatically assigns a // value to its ID field Contact c = new Contact(LastName = 'Weissman'); c.AccountId = a.Id; // The new contact now points at the new account insert c; // A SOQL query accesses data for the inserted contact, // including a populated c.account field c = [SELECT Account.Name FROM Contact WHERE Id = :c.Id]; // Now fields in both records can be changed through the contact c.Account.Name = 'salesforce.com'; c.LastName = 'Roth'; // To update the database, the two types of records must be // updated separately update c; // This only changes the contact's last name update c.Account; // This updates the account name
Note
The expression c.Account.Name, and any other expression that traverses a relationship, displays slightly different characteristics when it is read as a value than when it is modified:
When being read as a value, if c.Account is null, then c.Account.Name evaluates to null, but does not yield a NullPointerException. This design allows developers to navigate multiple relationships without the tedium of having to check for null values.
When being modified, if c.Account is null, then c.Account.Name does yield a NullPointerException.
In SOSL, you would access data for the inserted contact in a similar way to the SELECT statement used in the previous SOQL example.
*List<List<SObject>> searchList = [FIND 'Acme' IN ALL FIELDS RETURNING Contact(id,Account.Name)]*</SObject>
In addition, the sObject field key can be used with insert, update, or upsert to resolve foreign keys by external ID. For example this inserts a new contact with the AccountId equal to the account with the external_id equal to ‘12345’. If there is no such account, the insert fails.
Account refAcct = new Account(externalId_c = ‘12345’);
Contact c = new Contact(Account = refAcct, LastName = ‘Kay’);
insert c;
Understanding Foreign Key and Parent-Child Relationship SOQL Queries
The SELECT statement of a SOQL query can be any valid SOQL statement, including foreign key and parent-child record joins. If foreign key joins are included, the resulting sObjects can be referenced using normal field notation. For example:
System.debug([SELECT Account.Name FROM Contact WHERE FirstName = 'Caroline'].Account.Name);
Additionally, parent-child relationships in sObjects act as SOQL queries as well. For example:
for (Account a : [SELECT Id, Name, (SELECT LastName FROM Contacts) FROM Account WHERE Name = 'Acme']) { Contact[] cons = a.Contacts; } //The following example also works because we limit to only 1 contact for (Account a : [SELECT Id, Name, (SELECT LastName FROM Contacts LIMIT 1) FROM Account WHERE Name = 'testAgg']) { Contact c = a.Contacts; }
Working with SOQL Aggregate Functions
You can use aggregate functions without using a GROUP BY clause. For example, you could use the AVG() aggregate function to find the average Amount for all your opportunities.
~~~
AggregateResult[] groupedResults
= [SELECT AVG(Amount)aver FROM Opportunity];
Object avgAmount = groupedResults[0].get(‘aver’);
~~~
Note that any query that includes an aggregate function returns its results in an array of AggregateResult objects. AggregateResult is a read-only sObject and is only used for query results.
Aggregate functions become a more powerful tool to generate reports when you use them with a GROUP BY clause. For example, you could find the average Amount for all your opportunities by campaign.
~~~
AggregateResult[] groupedResults
= [SELECT CampaignId, AVG(Amount)
FROM Opportunity
GROUP BY CampaignId];
for (AggregateResult ar : groupedResults) {
System.debug(‘Campaign ID’ + ar.get(‘CampaignId’));
System.debug(‘Average amount’ + ar.get(‘expr0’));
}
~~~
Any aggregated field in a SELECT list that does not have an alias automatically gets an implied alias with a format expri, where i denotes the order of the aggregated fields with no explicit aliases. The value of i starts at 0 and increments for every aggregated field with no explicit alias.
Note
Queries that include aggregate functions are still subject to the limit on total number of query rows. All aggregate functions other than COUNT() or COUNT(fieldname) include each row used by the aggregation as a query row for the purposes of limit tracking.
For COUNT() or COUNT(fieldname) queries, limits are counted as one query row, unless the query contains a GROUP BY clause, in which case one query row per grouping is consumed.
Working with Very Large SOQL Queries
Your SOQL query sometimes returns so many sObjects that the limit on heap size is exceeded and an error occurs. To resolve, use a SOQL query for loop instead, since it can process multiple batches of records by using internal calls to query and queryMore.
For example, if the results are too large, this syntax causes a runtime exception:
Account[] accts = [SELECT Id FROM Account];
Instead, use a SOQL query for loop as in one of the following examples:
~~~
// Use this format if you are not executing DML statements
// within the for loop
for (Account a : [SELECT Id, Name FROM Account
WHERE Name LIKE ‘Acme%’]) {
// Your code without DML statements here
}
// Use this format for efficiency if you are executing DML statements
// within the for loop
for (List<Account> accts : [SELECT Id, Name FROM Account
WHERE Name LIKE 'Acme%']) {
for (Account a : accts) {
// Your code here
}
update accts;
}
~~~</Account>
The following example demonstrates a SOQL query for loop that’s used to mass update records. Suppose that you want to change the last name of a contact in records for contacts whose first and last names match specified criteria:
public void massUpdate() { for (List<Contact> contacts: [SELECT FirstName, LastName FROM Contact]) { for(Contact c : contacts) { if (c.FirstName == 'Barbara' && c.LastName == 'Gordon') { c.LastName = 'Wayne'; } } update contacts; } }
Instead of using a SOQL query in a for loop, the preferred method of mass updating records is to use batch Apex, which minimizes the risk of hitting governor limits.
More Efficient SOQL Queries
For best performance, SOQL queries must be selective, particularly for queries inside triggers. To avoid long execution times, the system can terminate nonselective SOQL queries. Developers receive an error message when a non-selective query in a trigger executes against an object that contains more than 1 million records. To avoid this error, ensure that the query is selective.
Selective SOQL Query Criteria
A query is selective when one of the query filters is on an indexed field and the query filter reduces the resulting number of rows below a system-defined threshold. The performance of the SOQL query improves when two or more filters used in the WHERE clause meet the mentioned conditions.
The selectivity threshold is 10% of the first million records and less than 5% of the records after the first million records, up to a maximum of 333,333 records. In some circumstances, for example with a query filter that is an indexed standard field, the threshold can be higher. Also, the selectivity threshold is subject to change.
Custom Index Considerations for Selective SOQL Queries
1. The following fields are indexed by default.
* Primary keys (Id, Name, and OwnerId fields)
* Foreign keys (lookup or master-detail relationship fields)
* Audit dates (CreatedDate and SystemModstamp fields)
* RecordType fields (indexed for all standard objects that feature them)
* Custom fields that are marked as External ID or Unique
- Fields not indexed by default are automatically indexed when the Salesforce optimizer recognizes that an index can improve performance for frequently run queries.
- Salesforce Support can add custom indexes on request for customers. A custom index can’t be created on these types of fields: multi-select picklists, currency fields in a multicurrency organization, long text fields, some formula fields, and binary fields (fields of type blob, file, or encrypted text.) New data types, typically complex ones, are periodically added to Salesforce, and fields of these types don’t always allow custom indexing.
- You can’t create custom indexes on formula fields that include invocations of the TEXT function on picklist fields.
- Typically, a custom index isn’t used in these cases.
* The queried values exceed the system-defined threshold.
* The filter operator is a negative operator such as NOT EQUAL TO (or !=), NOT CONTAINS, and NOT STARTS WITH.
* The CONTAINS operator is used in the filter, and the number of rows to be scanned exceeds 333,333. The CONTAINS operator requires a full scan of the index. This threshold is subject to change.
* You’re comparing with an empty value (Name != ‘’).
However, there are other complex scenarios in which custom indexes can’t be used.
Examples of Selective SOQL Queries
To better understand whether a query on a large object is selective or not, let’s analyze some queries. For these queries, assume that there are more than 1 million records for the Account sObject. These records include soft-deleted records, that is, deleted records that are still in the Recycle Bin.
SELECT Id FROM Account WHERE Id IN (<list>)</list>
SELECT Id FROM Account WHERE Name != ‘’
(Since Account is a large object even though Name is indexed (primary key), this filter returns most of the records, making the query non-selective.)
SELECT Id FROM Account WHERE Name != ‘’ AND CustomField__c = ‘ValueA’
(If the count of records returned by SELECT COUNT() FROM Account WHERE CustomField__c = ‘ValueA’ is lower than the selectivity threshold, and CustomField__c is indexed, the query is selective.)
Using SOQL Queries That Return One Record
SOQL queries can be used to assign a single sObject value when the result list contains only one element.
When the L-value of an expression is a single sObject type, Apex automatically assigns the single sObject record in the query result list to the L-value. A runtime exception results if zero sObjects or more than one sObject is found in the list. For example:
List<Account> accts = [SELECT Id FROM Account]; // These lines of code are only valid if one row is returned from // the query. Notice that the second line dereferences the field from the // query without assigning it to an intermediary sObject variable. Account acct = [SELECT Id FROM Account]; String name = [SELECT Name FROM Account].Name;
This usage is supported with the following Apex types, methods, or operators:
Database.query method.
Safe Navigation Operator.
Null Coalescing Operator.
Map.values
Improve Performance by Avoiding Null Values
In your SOQL and SOSL queries, explicitly filtering out null values in the WHERE clause allows Salesforce to improve query performance. In the following example, any records where the Thread_c value is null are eliminated from the search.
Public class TagWS { //getThreadTags - a quick method to pull tags not in the existing list public static webservice List<String> getThreadTags(String threadId, List<String> tags) { system.debug(LoggingLevel.Debug,tags); List<String> retVals = new List<String>(); Set<String> tagSet = new Set<String>(); Set<String> origTagSet = new Set<String>(); origTagSet.addAll(tags); // Note WHERE clause optimizes search where Thread\_\_c is not null for(CSOCaseThreadTagc t : [SELECT Name FROM CSOCaseThreadTagc WHERE Thread\_\_c = :threadId AND Thread\_\_c != null]) { tagSet.add(t.Name); } for(String x : origTagSet) { // return a minus version of it so the UI knows to clear it if(!tagSet.contains(x)) retVals.add('-' + x); } for(String x : tagSet) { // return a plus version so the UI knows it's new if(!origTagSet.contains(x)) retvals.add('+' + x); } return retVals; } }
Working with Polymorphic Relationships in SOQL Queries
A polymorphic relationship is a relationship between objects where a referenced object can be one of several different types. For example, the Who relationship field of a Task can be a Contact or a Lead.
The following describes how to use SOQL queries with polymorphic relationships in Apex.
You can use SOQL queries that reference polymorphic fields in Apex to get results that depend on the object type referenced by the polymorphic field. One approach is to filter your results using the Type qualifier. This example queries Events that are related to an Account or Opportunity via the What field.
~~~
List<Event> events = [SELECT Description FROM Event WHERE What.Type IN ('Account', 'Opportunity')];
~~~
Another approach would be to use the TYPEOF clause in the SOQL SELECT statement. This example also queries Events that are related to an Account or Opportunity via the What field.
~~~
List<Event> events = [SELECT TYPEOF What WHEN Account THEN Phone WHEN Opportunity THEN Amount END FROM Event];
~~~
These queries return a list of sObjects where the relationship field references the desired object types.
If you need to access the referenced object in a polymorphic relationship, you can use the instanceof keyword to determine the object type. The following example uses instanceof to determine whether an Account or Opportunity is related to an Event.
~~~
Event myEvent = eventFromQuery;
if (myEvent.What instanceof Account) {
// myEvent.What references an Account, so process accordingly
} else if (myEvent.What instanceof Opportunity) {
// myEvent.What references an Opportunity, so process accordingly
}
~~~</Event></Event>
Note that you must assign the referenced sObject that the query returns to a variable of the appropriate type before you can pass it to another method. The following example
Queries for User or Group owners of Merchandise_c custom objects using a SOQL query with a TYPEOF clause
Uses instanceof to determine the owner type
Assigns the owner objects to User or Group type variables before passing them to utility methods
public class PolymorphismExampleClass { // Utility method for a User public static void processUser(User theUser) { System.debug('Processed User'); } // Utility method for a Group public static void processGroup(Group theGroup) { System.debug('Processed Group'); } public static void processOwnersOfMerchandise() { // Select records based on the Owner polymorphic relationship field List<Merchandisec> merchandiseList = [SELECT TYPEOF Owner WHEN User THEN LastName WHEN Group THEN Email END FROM Merchandisec]; // We now have a list of Merchandisec records owned by either a User or Group for (Merchandisec merch: merchandiseList) { // We can use instanceof to check the polymorphic relationship type // Note that we have to assign the polymorphic reference to the appropriate // sObject type before passing to a method if (merch.Owner instanceof User) { User userOwner = merch.Owner; processUser(userOwner); } else if (merch.Owner instanceof Group) { Group groupOwner = merch.Owner; processGroup(groupOwner); } } } }
Using Apex Variables in SOQL and SOSL Queries
SOQL and SOSL statements in Apex can reference Apex code variables and expressions if they’re preceded by a colon (:). This use of a local code variable within a SOQL or SOSL statement is called a bind. The Apex parser first evaluates the local variable in code context before executing the SOQL or SOSL statement. Bind expressions can be used as:
The search string in FIND clauses.
The filter literals in WHERE clauses.
The value of the IN or NOT IN operator in WHERE clauses, allowing filtering on a dynamic set of values. Note that this is of particular use with a list of IDs or Strings, though it works with lists of any type.
The division names in WITH DIVISION clauses.
The numeric value in LIMIT clauses.
The numeric value in OFFSET clauses.
Note
Apex bind variables aren’t supported for the units parameter in the DISTANCE function. This query doesn’t work.
~~~
String units = ‘mi’;
List<Account> accountList =
[SELECT ID, Name, BillingLatitude, BillingLongitude
FROM Account
WHERE DISTANCE(My_Location_Field\_\_c, GEOLOCATION(10,10), :units) < 10];
~~~</Account>
Account A = new Account(Name='xxx'); insert A; Account B; // A simple bind B = [SELECT Id FROM Account WHERE Id = :A.Id]; // A bind with arithmetic B = [SELECT Id FROM Account WHERE Name = :('x' + 'xx')]; String s = 'XXX'; // A bind with expressions B = [SELECT Id FROM Account WHERE Name = :'XXXX'.substring(0,3)]; // A bind with INCLUDES clause B = [SELECT Id FROM Account WHERE :A.TYPE INCLUDES (‘Customer – Direct; Customer – Channel’)]; // A bind with an expression that is itself a query result B = [SELECT Id FROM Account WHERE Name = :[SELECT Name FROM Account WHERE Id = :A.Id].Name]; Contact C = new Contact(LastName='xxx', AccountId=A.Id); insert new Contact[]{C, new Contact(LastName='yyy', accountId=A.id)}; // Binds in both the parent and aggregate queries B = [SELECT Id, (SELECT Id FROM Contacts WHERE Id = :C.Id) FROM Account WHERE Id = :A.Id]; // One contact returned Contact D = B.Contacts; // A limit bind Integer i = 1; B = [SELECT Id FROM Account LIMIT :i]; // An OFFSET bind Integer offsetVal = 10; List<Account> offsetList = [SELECT Id FROM Account OFFSET :offsetVal]; // An IN-bind with an Id list. Note that a list of sObjects // can also be used--the Ids of the objects are used for // the bind Contact[] cc = [SELECT Id FROM Contact LIMIT 2]; Task[] tt = [SELECT Id FROM Task WHERE WhoId IN :cc]; // An IN-bind with a String list String[] ss = new String[]{'a', 'b'}; Account[] aa = [SELECT Id FROM Account WHERE AccountNumber IN :ss]; // A SOSL query with binds in all possible clauses String myString1 = 'aaa'; String myString2 = 'bbb'; Integer myInt3 = 11; String myString4 = 'ccc'; Integer myInt5 = 22; List<List<SObject>> searchList = [FIND :myString1 IN ALL FIELDS RETURNING Account (Id, Name WHERE Name LIKE :myString2 LIMIT :myInt3), Contact, Opportunity, Lead WITH DIVISION =:myString4 LIMIT :myInt5];
Querying All Records with a SOQL Statement
SOQL statements can use the ALL ROWS keywords to query all records in an organization, including deleted records and archived activities.
System.assertEquals(2, [SELECT COUNT() FROM Contact WHERE AccountId = a.Id ALL ROWS]);
You can use ALL ROWS to query records in your organization’s Recycle Bin. You cannot use the ALL ROWS keywords with the FOR UPDATE keywords.
SOQL For Loops
SOQL for loops iterate over all of the sObject records returned by a SOQL query.
The syntax of a SOQL for loop is either:
~~~
for (variable : [soql_query]) {
code_block
}
or
for (variable_list : [soql_query]) {
code_block
}
Examle:
String s = ‘Acme’;
for (Account a : [SELECT Id, Name from Account
where Name LIKE :(s+’%’)]) {
// Your code
}
~~~
SOQL For Loops Versus Standard SOQL Queries
SOQL for loops differ from standard SOQL statements because of the method they use to retrieve sObjects. While the standard queries discussed in SOQL and SOSL Queries can retrieve either the count of a query or a number of object records, SOQL for loops retrieve all sObjects, using efficient chunking with calls to the query and queryMore methods of SOAP API. Developers can avoid the limit on heap size by using a SOQL for loop to process query results that return multiple records. However, this approach can result in more CPU cycles being used. See Total heap size.
Queries including an aggregate function don’t support queryMore. A run-time exception occurs if you use a query containing an aggregate function that returns more than 2,000 rows in a for loop.
SOQL for loops can process records one at a time using a single sObject variable, or in batches of 200 sObjects at a time using an sObject list
The single sObject format executes the for loop’s <code_block> one time per sObject record. Consequently, it’s easy to understand and use, but is grossly inefficient if you want to use data manipulation language (DML) statements within the for loop body. Each DML statement ends up processing only one sObject at a time.
The sObject list format executes the for loop's <code_block> one time per list of 200 sObjects. Consequently, it’s a little more difficult to understand and use, but is the optimal choice if you must use DML statements within the for loop body. Each DML statement can bulk process a list of sObjects at a time.</code_block></code_block>
// Create a savepoint because the data should not be committed to the database Savepoint sp = Database.setSavepoint(); insert new Account[]{new Account(Name = 'yyy'), new Account(Name = 'yyy'), new Account(Name = 'yyy')}; // The single sObject format executes the for loop once per returned record Integer i = 0; for (Account tmp : [SELECT Id FROM Account WHERE Name = 'yyy']) { i++; } System.assert(i == 3); // Since there were three accounts named 'yyy' in the // database, the loop executed three times // The sObject list format executes the for loop once per returned batch // of records i = 0; Integer j; for (Account[] tmp : [SELECT Id FROM Account WHERE Name = 'yyy']) { j = tmp.size(); i++; } System.assert(j == 3); // The lt should have contained the three accounts // named 'yyy' System.assert(i == 1); // Since a single batch can hold up to 200 records and, // only three records should have been returned, the // loop should have executed only once // Revert the database to the original state Database.rollback(sp);