Comparing 2 CRM records to keep the difference

Hi everyone,

For some use cases, I had to perform some comparisons between 2 "versions" of the same record in order to keep the difference and use it.

Basically, in my particular scenario, we were exchanging data from a System to the CRM. And in order to know what was modified and not (to update only the necessary data), I had to perform a comparison between the record version within the CRM and the version I was getting from the other system.

Here is the process :

With JSON data format

For this section, the first important point is to make sure that we have the same format of data.
If we focus on the CRM WebApi, you have several ways to retrieve data.

In this particular case, make sure that you query the data from the WebApi using the header : Prefer: odata.include-annotations="*" in your request.

Regarding the data coming from your other system, you can transform it using acmemapper, the process is detailed into the article : "From JSON to CRM WebApi object using acmemapper"

In the code below, I'm loading the json data from some extra files, but in a real case, the input.json would be replaced by the data coming from the other System and the toCompare.json would be replaced by the CRM WebApi data.

var inputData = JObject.Parse(File.ReadAllText("input.json"));  
/* input.json would look like :
{
  "actualvalue": 3873600,
  "name": "3D Printer Purchase",
  "estimatedclosedate": "2016-05-08",
  "purchasetimeframe": 3,
  "opportunityid": "6634f32e-4be5-e511-80fe-00155d09ab01",
  "parentaccountid@databind": "/accounts(f5dfd502-037f-e511-80e7-3863bb347a80)"
}
*/
var toCompareData = JObject.Parse(File.ReadAllText("toCompare.json"));  
/* toCompare.json would look like : 
{
  "actualvalue": 3873600,
  "name": "3D Printer Purchase - Modified",
  "estimatedclosedate": "2016-05-08",
  "purchasetimeframe": 3,
  "opportunityid": "6634f32e-4be5-e511-80fe-00155d09ab01",
  "_parentaccountid_value@OData.Community.Display.V1.FormattedValue": "My best client",
  "_parentaccountid_value@Microsoft.Dynamics.CRM.lookuplogicalname": "account",
  "_parentaccountid_value": "f5dfd502-037f-e511-80e7-3863bb347a80"
}
*/

var differenceData = (JObject)inputData.DeepClone(); // we copy the input to remove nodes here

foreach (var property in inputData.Properties())  
{
    var propertyName = property.Name;
    var propertyValue = property.Value;

    var comparingValue = toCompareData[propertyName];

    // We check if the property is in the object to compare
    // if not, we skip it or it's a lookup
    if (comparingValue == null)
    {
        // If the property ends by @databind then it's a lookup
        // and there is a bit more of checks to perform
        if (propertyName.Contains("@databind"))
        {
            var propertyNameToCompare = $"_{propertyName.Replace("@databind", "")}_value";
            var propertyValueToCompare = toCompareData.SelectToken(propertyNameToCompare);
            var propertyEntityToCompare = toCompareData[$"{propertyNameToCompare}@Microsoft.Dynamics.CRM.lookuplogicalname"];

            if (propertyValueToCompare != null && propertyEntityToCompare != null)
            {
                // If we have either the entityname + the guid, we assume it's the same value !
                if (propertyValue.ToString().Contains(propertyEntityToCompare.ToString()) &&
                    propertyValue.ToString().Contains(propertyValueToCompare.ToString())
                )
                {
                    differenceData.Property(propertyName).Remove();
                }
            }
        }
    }
    // comparing the values now
    // if the values are the same, no need to update it in the CRM
    else if (propertyValue.Equals(toCompareData[propertyName]))
    {
        // so we remove the node from the difference result.
        differenceData.Property(propertyName).Remove();
    }
}

Console.WriteLine($"Here is my difference data :{Environment.NewLine}{differenceData}");  
/* Would display :
Here is my difference data :  
{
  "name": "3D Printer Purchase"
}
*/

The output data within the variable differenceData will contains the modified properties you could then use to perform any action inside the CRM, for example: an Update.

Which is not the case of the SDK Entity object as we will see just below.

With Entity object format

Assuming you have 2 Entity objects to compare in your hands, here is the snippet to spot the differences

// Retrieving data from the CRM for the related record
Entity inputEntity = _service.Retrieve("EntityName", EntityGuid, new Columnset(true));

// this will be the entity you retrieve from CRM or other system.
Entity toCompareEntity; 

// We instanciate a new object to extract the result
var differenceData = toCompareEntity;

bool isEntityModified = false;

for(int i = 0; i < toCompareEntity.Attributes.Count; i++)  
{
    var key = toCompareEntity.Attributes.ElementAt(i).Key;

    // if we have a key which is included in our 2 Entity objects, but the values are different,
    //then the entity was modified and we set the flag to true and just moving to the next property
    if (inputEntity == null || !inputEntity.Contains(key) ||
        (toCompareEntity.Contains(key) &&
         ((toCompareEntity[key] == null && inputEntity[key] != null) ||
          !toCompareEntity[key].Equals(inputEntity[key]))
        )
    )
        isEntityModified = true;
    // if the key is included in our 2 Entity objects AND the values are the same, no need to keep it,
    // we remove the attribute from the target Entity object
    else
        differenceData.Attributes.Remove(key);
}

The output of the Entity object "differenceData" will be the difference between the 2 Entity objects to keep only the relevant attributes.

Here, since we are comparing 2 objects which have the exact same format, it easier to perform comparison in order to get the differences, a "simple" toCompareEntity[key].Equals(inputEntity[key]) will do the job !

Conclusion

With few lines of code, you have the possibility to perform a comparison of 2 objects of the same kind.
The business needs can be really wide : data integrity, updating only relevant attributes and not the full entity, whatever you want !

Hope this helps,
Happy CRM'in.

Clement

Dynamics 365 CRM & Power Platform addict.