v3.0.0 - 31-January-2026
Scope
- Automatic Record-Level Dependency Resolution
- Performance Improvements
- Internal Architecture Refactoring
DML
- Automatic dependency resolution at the record level (not just SObject type level)
- Wave-based execution ensures parents are committed before dependents
- Optimal DML grouping minimizes the number of database operations
New Features
Automatic Record-Level Dependency Resolution
DML Lib now automatically tracks dependencies between individual records and executes them in optimized "waves". When you define relationships using withRelationship(), the library builds a dependency graph and ensures parent records are committed before their dependents.
Example: Complex Multi-Level Dependencies
Account parentAccount = new Account(Name = 'Parent');
Account childAccount = new Account(Name = 'Child');
Contact contact = new Contact(LastName = 'Smith');
Opportunity opportunity = new Opportunity(Name = 'Deal', StageName = 'New', CloseDate = Date.today());
new DML()
.toInsert(parentAccount)
.toInsert(DML.Record(childAccount).withRelationship(Account.ParentId, parentAccount))
.toInsert(DML.Record(contact).withRelationship(Contact.AccountId, childAccount))
.toInsert(DML.Record(opportunity).withRelationship(Opportunity.AccountId, childAccount))
.commitWork();
// Execution waves:
// Wave 1: parentAccount (no dependencies)
// Wave 2: childAccount (depends on parentAccount)
// Wave 3: contact, opportunity (both depend on childAccount)Wave-Based Execution
Records are now organized into execution waves based on their dependencies. Records in the same wave that share the same operation type and SObject type are batched into a single DML statement.
Example: Optimal DML Grouping
new DML()
.toInsert(account1)
.toInsert(account2)
.toUpsert(account3)
.toUpsert(account4)
.toInsert(DML.Record(account5).withRelationship(Account.ParentId, account2))
.toInsert(DML.Record(contact1).withRelationship(Contact.AccountId, account2))
.toInsert(DML.Record(contact2).withRelationship(Contact.AccountId, account3))
.toInsert(opportunity1)
.toInsert(DML.Record(opportunity2).withRelationship(Opportunity.AccountId, account4))
.toInsert(lead1)
.commitWork();
// Result: 7 DML statements executed (optimally grouped)| DML # | Operation | SObject | Records | Reason |
|---|---|---|---|---|
| 1 | INSERT | Account | account1, account2 | No dependencies, same bucket |
| 2 | UPSERT | Account | account3, account4 | No dependencies, different operation |
| 3 | INSERT | Opportunity | opportunity1 | No dependencies |
| 4 | INSERT | Lead | lead1 | No dependencies |
| 5 | INSERT | Account | account5 | Depends on account2 |
| 6 | INSERT | Contact | contact1, contact2 | Depend on account2 and account3 |
| 7 | INSERT | Opportunity | opportunity2 | Depends on account4 |
Improvements
Validation Improvements
Added null check validation when creating records by ID:
// Now throws: "Invalid argument: recordId. Record ID cannot be null."
DML.Record(null);Documentation
- Updated Registration documentation with detailed explanation of the dependency graph algorithm
- Added visual diagrams showing wave-based execution
- Added examples demonstrating optimal DML grouping
🚨 Breaking Changes 🚨
Removed: Custom Execution Order Constructor
The constructor that accepted a custom execution order has been removed:
// ❌ No longer available
new DML(new List<SObjectType>{ Account.SObjectType, Contact.SObjectType });
// ✅ Use automatic dependency resolution instead
new DML()
.toInsert(account)
.toInsert(DML.Record(contact).withRelationship(Contact.AccountId, account))
.commitWork();Migration: Remove the custom execution order parameter. The library now automatically determines the correct execution order based on defined relationships.
Changed: Dependency Detection
Previously, the library tracked dependencies at the SObject type level. Now it tracks at the record level, which provides more accurate execution ordering but may result in different execution patterns for complex scenarios.
Before (v2.x): All Accounts inserted first, then all Contacts
After (v3.x): Records inserted in waves based on actual dependencies between specific records
Internal Refactoring
- Replaced
DmlCommandclasses withDmlStrategypattern for cleaner separation of concerns - Split orchestration into
DependencyOrchestrator(INSERT/UPSERT) andLinearOrchestrator(UPDATE/DELETE/UNDELETE/MERGE/PUBLISH) - Introduced
StrategiesStoragefor strategy instance management - Changed from Kahn's algorithm on SObject types to Kahn's algorithm on individual records
- Improved
RandomIdGeneratorwith deterministic counter-based ID generation - Added
ProcessingGroupclass for efficient record batching - Introduced
ExecutionResultandSObjectAggregatedResultfor better result handling - Copyright year updated to 2026