After moving some Apex code to API 67, I started seeing a DML error that looked like a straightforward permission problem:
System.DmlException: Operation failed due to fields being inaccessible on Sobject ProjectTask__c, check errors on Exception or Result!
The user could edit ProjectTask__c, and the field being changed was editable. Adding more permissions did not make a difference.
The code that failed
Here is a simplified version:
ProjectTask__c task = [
SELECT Id, Project__c, Status__c
FROM ProjectTask__c
WHERE Id = :taskId
FOR UPDATE
];
task.Status__c = 'Completed';
update task;
Project__c is a required, non-reparentable master-detail field. It has to be provided when the task is created, but it cannot be changed later. Because it is not updateable, Salesforce does not offer it as an editable field permission.
The surprising part was that the code never changed Project__c. It only changed Status__c.
The queried record still carried Project__c, however, and that field was included in the SObject passed to DML. Under user-mode field checks, its presence was enough to make the update fail.
Finding the actual field
DmlException.getDmlFieldNames() made this much easier to diagnose:
try {
update task;
} catch (DmlException error) {
System.debug(error.getDmlFieldNames(0));
}
The output named Project__c, not Status__c.
I then repeated the update with a fresh record containing only the ID and the field I wanted to change. That update worked.
Use a sparse update
The simplest fix was to stop updating the queried instance:
update new ProjectTask__c(
Id = task.Id,
Status__c = 'Completed'
);
This is often called a sparse update. It has a useful side effect beyond fixing the error: anyone reviewing the code can see exactly which fields the operation is allowed to change.
For code that handles a more varied set of records, Security.stripInaccessible can provide another boundary:
List<SObject> updateableRecords = Security
.stripInaccessible(
AccessType.UPDATABLE,
records,
true
)
.getRecords();
update updateableRecords;
The third argument tells Salesforce to enforce update access on the object itself. Fields the user cannot update are removed before DML.
I prefer sparse records when the write set is known. stripInaccessible is more useful when records arrive with different populated fields and rebuilding every record would add unnecessary complexity.
Required does not mean updateable
That distinction was the part I had overlooked.
A master-detail field can be:
- required when a child record is inserted;
- visible when the child is queried; and
- invalid in an update payload when reparenting is disabled.
So if user-mode DML reports an inaccessible field, check getDmlFieldNames(0) before changing permission sets. The problem may be a field that should not have been sent to the update at all.
0 comments:
Post a Comment