5

Use Case:

Need to allow changes to only one specific field on an opportunity when at a certain stage.

Problem:

How to efficiently determine within a trigger context that a only the specific field has changed and no other values have changed.

Info:

Salesforce's documentation on Sobject equality states that Sobject1 == Sobject2 will perform a field by field comparison of the two sobjects. Though my testing seems to indicate that Salesforce's is performing this field by field comparison with more fields than is returned by performing a metadata describe. As when I perform a metadata describe to get the Sobjects set of fields and iterate said set comparing the two Sobjects values I get a result indicating the two Sobjects are equal. Yet when I do the check of Sobject1 == Sobject2 it comes back false.

Questions:

Does a trigger context Sobject(as in the Sobjects in trigger.new) contain extra values that would cause an equality check to fail despite all metadata describe visible fields being equal?

Conversely does a Sobject clone contain extra values that would cause an equality check to fail despite all metadata describe visible fields being equal?

Why would the following not result as being equal assuming only the Opportunity Type field has changed?

Opportunity oldOppClone = trigger.old[0].clone(true,true,true,true);

oldOppClone.Type = trigger.new[0].Type;

system.assert(trigger.new[0]==oldOppClone);

Is there any other way to facilitate obtaining a set of fields which have changed between two Sobjects than metadata describing and iterating the fields and checking each individually?

  • Efficiently? Not possible. – Adrian Larson Feb 12 '18 at 21:06
  • Thanks all who responded sfdcfox's answer is the closest to what I went with which was metadata describe all fields and iterate to find the differences. – gussamer Feb 20 '18 at 22:52
1

The Trigger.old and Trigger.new values will always differ; even after running SObject.clone(false, false, false, false), which clears out autonumber fields, and the four audit fields, the SystemModStamp will survive the cloning process, meaning that a simple equality check (oldRecord == newRecord) will fail. A better idea would be to set up a Field Set, and check just the values in the field set against each other:

for(Integer index = 0, size = Trigger.new.size(); index < size; index++) {
  Opportunity oldRecord = Trigger.old[index], newRecord = Trigger.new[index];
  for(FieldSetMember m: Schema.Opportunity.fieldSets.someFieldSet.getFields()) {
    if(oldRecord.get(m.getFieldPath()) != newRecord.get(m.getFieldPath()) {
      // Something is different here
| improve this answer | |
3

It will likely be inefficient agonizingly slow, but you might want to loop through the results of the SObject.getPopulatedFieldsAsMap() method.

public with sharing class ModificationTracker
{
    final Set<String> toIgnore = new Set<String>();
    public ModificationTracker ignore(SObjectField field)
    {
        return ignore(String.valueOf(field))
    }
    public ModificationTracker ignore(String field)
    {
        toIgnore.add(field);
        return this;
    }

    public List<SObject> getModified(List<SObject> records, Map<Id, SObject> records)
    {
        List<SObject> modified = new List<SObject>();
        for (SObject record : records)
        {
            if (isModified(newRecord, oldRecord))
            {
                modified.add(newRecord);
            }
        }
        return modified;
    }
    Boolean isModified(SObject newRecord, SObject oldRecord)
    {
        Map<String, Object> values = newRecord.getPopulatedFieldsAsMap();
        for (String field : values.keySet())
        {
            if (value != oldRecord.get(field))
            {
                return true;
            }
        }
        return false;
    }
}

I'm not sure if this method will ignore LastModifiedDate and other audit fields, you'd have to play around with the toIgnore blacklist to get the behavior correct.

The implementation would look like:

List<Opportunity> modifiedRecords = new ModificationTracker()
    .ignore(Opportunity.LastModifiedDate) // if necessary
    .ignore(Opportunity.CustomField__c)
    .getModified(trigger.new, trigger.oldMap);
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.