XrmContext icon indicating copy to clipboard operation
XrmContext copied to clipboard

Attribute Change Management

Open bo-stig-christensen opened this issue 4 years ago • 0 comments

The Problem Currently we need to write explicit code to handle updating only changed attributes on an entity. Some developers do it, others take the easy way out and doesn't. - This results in audit log and potentially performance suffering with attributes being updated that do not need to, which in turn also may trigger additional plugins and flows unintentionally. An example of current necessary code to detect changes and update 3 attributes:

var newActualValue = 22222;
var newActualCloseDate = DateTime.Now;
var newBudgetAmount = 12345;
var opportunityFromD365 = Opportunity.Retrieve(OrganizationService, opportunityId);

var updOpportunity = new Opportunity(opportunityId);
if (opportunityFromD365.ActualValue != newActualValue)
    updOpportunity.ActualValue = newActualValue;
if (opportunityFromD365.ActualCloseDate != newActualCloseDate)
    updOpportunity.ActualCloseDate = newActualCloseDate;
if (opportunityFromD365.BudgetAmount != newBudgetAmount)
    updOpportunity.BudgetAmount = newBudgetAmount;

OrganizationAdminService.Update(updOpportunity);

Much of the time, what I see is that a standard object initializer is used instead and no explicit check for changed attribute values are added.

The Solution It would be nice to be able to write the following code instead:

var newActualValue = 22222;
var newActualCloseDate = DateTime.Now;
var newBudgetAmount = 12345;
var opportunity = Opportunity.Retrieve(OrganizationService, opportunityId);
opportunity.ActualValue = newActualValue;
opportunity.ActualCloseDate = newActualCloseDate;
opportunity.BudgetAmount = newBudgetAmount;
opportunity.Update(OrganizationService);

In this case I would expect the following from the generated entity classes:

  1. An internal "ChangedAttributes" field, which is maintained by Property Setter implementation on each attribute's property.
  2. An internal "OriginalAttributes" field, which contains the initial values fetched from D365.
  3. Each attribute property must have a setter that compares the set value with the original value from D365 and updates the ChangedAttributes collection accordingly (adds or removes item).
  4. A Public GetChangedEntity() method that returns an entity instance with only the changed attributes (based on ChangedAttributes collection, not a D365 call).
  5. A Public Update() method that basically uses GetChangedEntity() and calls the service.Update() method as well as doing some housekeeping like clearing the ChangedAttributes collection.

Another potential strategy could be to optionally allow the entity.Update() method to retrieve the entity fresh from D365 based on a method parameter, extrapolate the changedEntity and run an update. - However, this strategy will generate an additional API call, which may not always be what we want due to limits and performance ;-)

Key benefits

  1. Avoid Audit Log spamming with Old and New values being the same.
  2. Avoid plugins, workflows and flows triggering unintentionally.
  3. Slightly less boilerplate code each time we update an entity.
  4. Move responsibility of implementing clean updates from developer to framework, which will minimize issues related to developer error and/or laziness, with a possibility to explicitly override if needed.

bo-stig-christensen avatar Nov 28 '19 09:11 bo-stig-christensen