100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

sObjects and DML

Understand Apex's sObject data model and the insert, update, upsert, and delete DML operations used to change Salesforce records, including bulk patterns and exception handling.

Control Flow & CollectionsIntermediate11 min readJul 10, 2026
Analogies

What is an sObject?

An sObject is Apex's generic representation of any Salesforce record — standard objects like Account, Contact, and Opportunity, or custom objects like Invoice__c, all inherit from the base sObject type. You can declare fields dynamically with new Account(Name = 'Acme Corp', Industry = 'Technology') and every sObject automatically carries system fields like Id, CreatedDate, and LastModifiedDate once it's been inserted, with Id remaining null until the DML operation completes.

🏏

Cricket analogy: A player registration form used by the ICC works the same for a batter, bowler, or all-rounder, one generic template with a name and role, the same way sObject is the generic base type underlying Account, Contact, and every custom object.

apex
// Creating and inserting a new sObject record
Account newAcct = new Account(
    Name = 'Acme Corp',
    Industry = 'Technology',
    AnnualRevenue = 5000000
);
insert newAcct;
System.debug('New Account Id: ' + newAcct.Id); // populated after insert

// Generic sObject variable — useful for dynamic code
SObject genericRecord = newAcct;
String acctName = (String) genericRecord.get('Name');

// Custom object example
Invoice__c inv = new Invoice__c(
    Account__c = newAcct.Id,
    Amount__c = 1500.00,
    Status__c = 'Draft'
);
insert inv;

DML: insert, update, upsert, delete, undelete

Data Manipulation Language (DML) statements are how Apex persists changes: insert creates new records, update modifies existing ones (requires a populated Id), upsert does either based on Id or an External ID field, delete moves records to the Recycle Bin, and undelete restores them within 15 days. Every DML operation is bulk-capable — insert accountList; works identically whether the list has 1 record or 200, which is exactly why the bulk patterns from Lists and Maps matter so much in real trigger code.

🏏

Cricket analogy: A team's official squad announcement (insert) differs from a mid-series replacement announcement (update) and a final squad-list correction that adds or replaces a name based on jersey number (upsert), each a distinct roster operation.

upsert is especially valuable for data integration scenarios where an external system feeds records into Salesforce repeatedly — you mark a custom field as an External ID (e.g. SAP_Id__c) and call upsert accountList SAP_Id__c;, letting Salesforce decide per-record whether to insert or update based on whether that External ID value already exists. Without upsert, integration code would need to query first to check existence, adding an extra SOQL query per sync run.

🏏

Cricket analogy: A stadium's ticketing system reconciling a season-ticket holder's renewal either creates a fresh membership or updates the existing one based on a unique member number, exactly like upsert keyed on an External ID.

Database.insert(), Database.update(), and Database.upsert() are the 'partial success' variants of DML. Unlike the plain insert/update statements, which throw an exception and roll back the whole batch on any single record failure, the Database.* methods accept a Boolean allOrNothing parameter — passing false lets valid records succeed while failed ones are reported individually via a returned Database.SaveResult[] array.

apex
List<Account> accountsToInsert = new List<Account>{
    new Account(Name = 'Globex'),
    new Account(Name = null) // will fail: Name is required
};

Database.SaveResult[] results = Database.insert(accountsToInsert, false);

for (Integer i = 0; i < results.size(); i++) {
    if (!results[i].isSuccess()) {
        for (Database.Error err : results[i].getErrors()) {
            System.debug('Row ' + i + ' failed: ' + err.getMessage());
        }
    }
}

// Upsert against an External Id field for integration sync
List<Account> externalAccounts = new List<Account>{
    new Account(SAP_Id__c = 'SAP-001', Name = 'Acme via SAP')
};
upsert externalAccounts SAP_Id__c;

A plain update statement on a list throws a DmlException and rolls back the entire operation if even one record among hundreds fails validation. In trigger and batch code where partial success is acceptable, use Database.update(records, false) and inspect the SaveResult array instead of letting one bad record block 199 good ones.

Trigger context and sObject state

Inside a trigger, Trigger.new holds the list of sObjects in their new state (insert/update contexts) and Trigger.old holds the previous state (update/delete contexts), both accessible alongside Trigger.newMap and Trigger.oldMap for Id-keyed lookups. A common pattern is comparing Trigger.new and Trigger.old field-by-field to detect actual changes, since an update DML call fires even if no field values actually changed, and you generally don't want side effects re-running unnecessarily.

🏏

Cricket analogy: Comparing a player's fitness report before and after a training camp (Trigger.old vs Trigger.new) tells the coach exactly what changed, rather than assuming every report update means something meaningful shifted.

  • sObject is the generic base type underlying every standard object (Account, Contact) and custom object (Invoice__c).
  • insert creates records, update modifies existing ones by Id, upsert creates-or-updates based on Id or an External ID field, and delete/undelete manage the Recycle Bin lifecycle.
  • All DML statements are inherently bulk-capable, operating on a List of up to 10,000 records in a single call (subject to the 150 DML statement limit).
  • Database.insert/update/upsert with allOrNothing = false enable partial success, returning a SaveResult[] to inspect per-record errors.
  • A plain insert/update statement throws and rolls back the entire operation if any single record fails.
  • Trigger.new/old and their Map variants (newMap/oldMap) give access to a record's before/after state inside triggers.
  • Comparing Trigger.old and Trigger.new field values prevents automation logic from re-firing on no-op updates.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ApexSalesforceStudyNotes#SObjectsAndDML#SObjects#DML#SObject#Insert#StudyNotes#SkillVeris#ExamPrep