Power Automate: Loop Through the Dataverse Child Records and Analyse input/output

Let’s see how we can loop through the child records returned from the Dataverse, and analyse the input / outputs at each step.

Following is the example scenario we will cover.

On deactivation of Account record we want to loop through the related opportunities.

Following are the steps we will cover:

Create Cloud Flow on Account Deactivate

Create a automated cloud flow.

From outside solution.

or from inside solution.

Select Microsoft Dataverse connector and choose trigger

“When a row is added, modified or deleted”

Because this flow will trigger on deactivate, which is update of account.

Update Trigger properties

Set the flow properties as following:

First of all rename the step to Account or anything else meaningful, to easily identify this step.

Then we need to fill step properties

  • Change type: Modified
    • This defines the on change type on which this flow will trigger.
    • Other options are as following
  • Table name: Accounts
    • This option defines on which table we want flow to run.
    • Any table can be selected
  • Scope: Organization
    • This defines the scope of flow, for which users this flow will trigger.
    • Select organization if this flow should trigger for all users.
  • Select columns: statecode
    • Specify comma separated list of columns.
    • Flow will trigger If any of them are modified.
  • Filter rows: statecode eq 1
    • We are specifying flow to run when statecode is 1, which is inactive status for account.
    • In this you specify OData style filter to determine eligible rows.
  • Run as: Modifying user
    • Specify under which user context flow will run.

So far, it should look like following.

Retrieve child opportunities

Next add the Dataverse action to list rows as following.

Rename the step to List opportunities, to identify the step.

Update the List Opportunities step properties as following:

  • Table name: opportunities
    • Specifies which table records we want to retrieve, opportunity in this case.
  • Select columns: name
    • Specify the columns we want to retrieve, it’s good idea to retrieve only the required columns. In this case we are retrieving name column only.
  • Filter rows: _parentaccountid_value eq [Account from Dynamics content]
    • Specify OData style filter to filter rows.
    • Here we have specified to retrieve only the opportunity rows with parent account id matching to triggering account record id.

We can specify more properties as needed, but for this example leaving as it is.

Analyse value, body and body/value – item from list rows step

List rows step returns following Dynamics content.

  • value: value returns the array of records from the specified table in json format.
  • body: body will return the same array of records along with some other properties in body.
  • body/value – item: this contains a single instance of the array item which is single opportunity record in this case.
    • notice when we add this, automatically Apply to each step will be added.

Let’s analyse each of them.

Add three compose steps for each of them

Notice, when we add body/value – item, automatically Apply to each step will be added, and compose step will be nested inside with current item dynamic content.

Add one more step inside apply to each to extract the opportunity id

Set the expression as following to get the opportunityid from opportunity.

This should appear like this, so far.

Update each opportunity record

Next, for example we want to update current opportunity record, current item in the loop. We can do as following.

Add a Dataverse – Update a row step inside Apply to each.

and set the Table name and Row ID properties as following.

Output is from opportunity id compose item in Dynamics content window.

Here we are specifying:

  • Table name: opportunity
    • We want to update the opportunity record.
  • Row ID: id of the opportunity record we want to update, current item id in this case.

We can update any other property as we want to update on this record and same will be updated in Dataverse.

It should appear like this by now,

Save this flow

Analyse flow input / output

To test the flow and analyse the input / output, deactivate an Account record having few opportunities.

Account step

on clicking Show raw inputs

We can see the parameters passed to Dataverse.

On clicking on Show raw outputs.

List of opportunities step

On clicking Show raw inputs

We can see the filter is applied while retrieving the list of opportunities.

On click to download, json. it we can analyse all the returned data from Dataverse.

{
    "statusCode": 200,
    "headers": {
        "Vary": "Accept-Encoding",
        "x-ms-service-request-id": "e20b3c32-6708-414a-8e2a-533514cdf8e4,78409faa-75a8-4dbe-a535-b289b3327575",
        "Cache-Control": "no-cache",
        "Set-Cookie": "ARRAffinity=a50f3777a483fd4e96aadf1d0e2923f38abbc01c3df5d6cb731bbcf61f629039; domain=org1f7aa4d6.crm11.dynamics.com; path=/; secure; HttpOnly,ReqClientId=68e2c649-02b0-4fcc-89e8-d274f8d5c7bc; expires=Wed, 12-Aug-2071 20:39:52 GMT; path=/; secure; HttpOnly,ARRAffinity=a50f3777a483fd4e96aadf1d0e2923f38abbc01c3df5d6cb731bbcf61f629039; domain=org1f7aa4d6.crm11.dynamics.com; path=/; secure; HttpOnly",
        "Strict-Transport-Security": "max-age=31536000; includeSubDomains",
        "REQ_ID": "78409faa-75a8-4dbe-a535-b289b3327575",
        "AuthActivityId": "1c30924d-64e5-45eb-b690-a28fbdf484af",
        "x-ms-dop-hint": "4",
        "x-ms-ratelimit-time-remaining-xrm-requests": "1,198.57",
        "x-ms-ratelimit-burst-remaining-xrm-requests": "7995",
        "OData-Version": "4.0",
        "Preference-Applied": "odata.include-annotations=\"*\"",
        "X-Source": "1113514625493361791435986622061331061211582156719719922214047249116202212155240124105180,1113514625493361791435986622061331061211582156719719922214047249116202212155240124105180",
        "Public": "OPTIONS,GET,HEAD,POST",
        "Timing-Allow-Origin": "*",
        "Date": "Thu, 12 Aug 2021 20:39:53 GMT",
        "Allow": "OPTIONS,GET,HEAD,POST",
        "Content-Type": "application/json; odata.metadata=full",
        "Expires": "-1",
        "Content-Length": "1702"
    },
    "body": {
        "@odata.context": "https://org1f7aa4d6.crm11.dynamics.com/api/data/v9.1/$metadata#opportunities(name)",
        "#Microsoft.Dynamics.CRM.DeleteMultiple": {
            "title": "DeleteMultiple",
            "target": "https://org1f7aa4d6.crm11.dynamics.com/api/data/v9.1/opportunities/Microsoft.Dynamics.CRM.crmbaseentity/Microsoft.Dynamics.CRM.DeleteMultiple"
        },
        "@Microsoft.Dynamics.CRM.totalrecordcount": -1,
        "@Microsoft.Dynamics.CRM.totalrecordcountlimitexceeded": false,
        "value": [
            {
                "@odata.type": "#Microsoft.Dynamics.CRM.opportunity",
                "@odata.id": "https://org1f7aa4d6.crm11.dynamics.com/api/data/v9.1/opportunities(b052fc98-e8f0-ea11-a815-000d3a1b14a2)",
                "@odata.etag": "W/\"1774838\"",
                "@odata.editLink": "opportunities(b052fc98-e8f0-ea11-a815-000d3a1b14a2)",
                "name": "18 Airpot Coffee Makers for Northwind Traders",
                "opportunityid@odata.type": "#Guid",
                "opportunityid": "b052fc98-e8f0-ea11-a815-000d3a1b14a2"
            },
            {
                "@odata.type": "#Microsoft.Dynamics.CRM.opportunity",
                "@odata.id": "https://org1f7aa4d6.crm11.dynamics.com/api/data/v9.1/opportunities(2ec06197-31ec-ea11-a817-000d3a1b14a2)",
                "@odata.etag": "W/\"1773955\"",
                "@odata.editLink": "opportunities(2ec06197-31ec-ea11-a817-000d3a1b14a2)",
                "name": "2 Café Corto for Northwind Traders",
                "opportunityid@odata.type": "#Guid",
                "opportunityid": "2ec06197-31ec-ea11-a817-000d3a1b14a2"
            },
            {
                "@odata.type": "#Microsoft.Dynamics.CRM.opportunity",
                "@odata.id": "https://org1f7aa4d6.crm11.dynamics.com/api/data/v9.1/opportunities(3cbbd39d-d3f0-ea11-a815-000d3a33f3c3)",
                "@odata.etag": "W/\"1774047\"",
                "@odata.editLink": "opportunities(3cbbd39d-d3f0-ea11-a815-000d3a33f3c3)",
                "name": "5 Café BG-1 Pro Grinders for Northwind Traders",
                "opportunityid@odata.type": "#Guid",
                "opportunityid": "3cbbd39d-d3f0-ea11-a815-000d3a33f3c3"
            }
        ]
    }
}

Compose step – Value

Show raw inputs

Value Dynamic content contains all the returned rows.

Show raw outputs

Compose Step – Body

Show raw inputs

Body Dynamic content contains all the returned rows along with some other body properties.

Show raw outputs

Apply to each step

Show raw inputs

We can see a single opportunity record inside apply to each.

Apply to each – opportunityid

Apply to each – Update a row

Show raw inputs

We can see what parameters were passed as update request to Dataverse.

Show raw outputs

Conclusion

In this post we learned how we can loop through the list of child records. And on each step we analysed what are input/output to each step visualize what is going in and out.

Record Merging and Merge Tracking in Depth in Microsoft Dynamics 365

In Dynamics 365 we capture data and data can easily become duplicate for multiple reasons such as bulk record import.

Microsoft Dynamics 365 provides OOB merge functionality which is quite helpful in deduplication of the records and cleaning up the data.

Merging in Dynamics 365

In Dynamics 365 we can merge Account, Contact, Leads or Incidents.

Merging process in Dynamics 365 takes two records of same the entity, the Master record and the Subordinate record, and you specify which is the master record out of two.

On merging the subordinate record gets deactivated and linked to the master record any related records from subordinate record moves to the master record.

Let’s explore more about the merging process and extend the merge tracking in Dynamics 365

In this blog we will cover:

  • Merge process in Dynamic 365 for accounts, contacts and leads.
  • Merge process in Dynamics 365 for incidents.
  • Merge triggers in Dynamics 365.
    • Manual selection of records in grid
    • Duplicate detection rule configuration
    • Calling MergeRequet SDK Message
    • Calling Web API Merge Action
  • The new enhanced merging experience and options in Dynamics 365.
    • New options
      • Merge records by choosing fields with data
      • View fields with conflicting data
      • Enable Parent Check
      • Select all fields in this section
  • Limitations of merging in Dynamics 365.
  • Hidden merge tracking fields in Dynamics 365.
  • Extending merge tracking functionality in Dynamics 365.
    • Create a system view to show Master and Subordinate record in a view.
    • Create visualization for merged records.
    • Record merged fields and subordinate record reference on master record for tracking.
  • Security considerations for Merging in Dynamics 365.

Merge process in Dynamic 365 for account, contacts and leads.

When you choose to merge Accounts, Contacts or Leads you are presented with following screen.

On Merging screen you specify which is the Master (primary) record, and the other one becomes the Subordinate record. Optionally you can specify any subordinate records fields to override the master records fields, by default all the master records fields will be selected.

On the merge screen you are also presented with following options which we will discuss later in this blog.

  • Merge records by choosing fields with data
  • View fields with conflicting data
  • Enable Parent Check
  • Select all fields in this section

When you have chosen the master record and fields, you can click on Ok to merge.

Merge process in Dynamics 365 for incidents.

Merging process for Incidents is a bit different from Account, Contact and Leads.

For merging incidents you notice few differences such as:

  • You have to select mater record from the grid to which other record will merge into.
  • You don’t get option to choose fields from the subordinate record to move onto the master record.
  • There are no new option as they are available for account, contact and lead merging.

Different merge triggers in Dynamics 365

There are multiple triggers to initiate merging process such as:

  • Manual selection of records in grid
  • Duplicate detection rule configuration
  • Calling MergeRequet SDK Message
  • Calling Web API Merge Action

Manual selection of records in grid

In manual merging you select two records in grid and then click on Merge button to launch merge screen.

Duplicate detection rule configuration

We can set up duplicate detection rule, which will launch the merge screen if duplicate records are detected as per the rule.

Calling MergeRequet SDK Message

You can merge records programmatically by calling MergeRequet message available in the SDK.

The following example in C# shows how we can merge two record using MergeRequest message.

// Create the target for the request.
              var target = new EntityReference();
// Id is the GUID of the account that is being merged into.
      // LogicalName is the type of the entity being merged to, as a string
             target.Id = _account1Id;
             target.LogicalName = Account.EntityLogicalName;
// Create the request.
      var merge = new MergeRequest();
      // SubordinateId is the GUID of the account merging.
      merge.SubordinateId = _account2Id;
      merge.Target = target;
      merge.PerformParentingChecks = false;
Console.WriteLine("\nMerging account2 into account1 and adding " + "\"test\" as Address 1 Line 1");
// Create another account to hold new data to merge into the entity.
      // If you use the subordinate account object, its data will be merged.
      var updateContent = new Account();
      updateContent.Address1_Line1 = "test";
// Set the content you want updated on the merged account
      merge.UpdateContent = updateContent;
// Execute the request.
      var merged = (MergeResponse)svc.Execute(merge);

Note: UpdateContent property will not be applicable to incidents and will be ignored.

Calling Merge Action using Web API

The following example in TypeScript shows how we can call the Merge Action available in the Web API to merge records.

    export async function ContactMerge() {
        debugger;
        const targetId: string= "71a17064-1ae7-e611-80f4-e0071b661f01"; // replace with target Id
        const subordinateId: string= "73a17064-1ae7-e611-80f4-e0071b661f01"; // replace with subordinate Id
        const contactMergeRequest: any = {};
        contactMergeRequest.Target = {
            entityType: "contact",
            id: targetId
        }
        contactMergeRequest.Subordinate = {
            entityType: "contact",
            id: subordinateId
        }
        contactMergeRequest.UpdateContent = {
            jobtitle: "{Upated Job title}",
             "@odata.type": "Microsoft.Dynamics.CRM.contact"
        }
        contactMergeRequest.PerformParentingChecks = false;
        contactMergeRequest.getMetadata = function () {
            return {
                boundParameter: null,
                parameterTypes: {
                    "Target": {
                        "typeName": "mscrm.contact",
                        "structuralProperty": 5
                    },
                    "Subordinate": {
                        "typeName": "mscrm.contact",
                        "structuralProperty": 5
                    },
                    "PerformParentingChecks":{
                        "typeName": "Edm.Boolean",
                        "structuralProperty": 1
                    },
                    "UpdateContent":{
                        "typeName": "mscrm.contact",
                        "structuralProperty": 5
                    }
                },
                operationType: 0,
                operationName: "Merge"
            }
        }
        const response = await Xrm.WebApi.online.execute(contactMergeRequest)
        console.log(response);
    }

The new enhanced merging experience and options in Dynamics 365.

When you select two records (Contacts in this case) in grid and click on Merge button following screen appears which has a few new options.

New Merge screen

The merge screen provides few new options, and provides opportunity to choose fields from master or subordinate records which will be saved finally on master record.

When you click Ok, merging begins.

and on completion success message appears as following.

The selected records are merged and subordinate record is deactivated.

As you can see the new enhanced merging experience provides four new options as following:

  • Merge records by choosing fields with data
  • View fields with conflicting data 
  • Select all fields with conflicting data
  • Enable Parent Check

Let’s discuss each of them.

Merge records by choosing fields with data

If you select this option, fields from master or subordinate which has the data in same field is selected, if both the records have the data in same field then master record field is selected.

View fields with conflicting data 

If you select this option then only fields with different data will be shown. Fields with same data will be hidden.

Select all fields in this section

If you select this, then all the fields under this section will get selected. This is helpful if you have many fields under a section to select.

Enable Parent Check

This is interesting.

If this option is checked and the records have different parent Accounts, then on merging following error will be thrown.

Error

Unable to merge because sub-entity will be parented differently. You can disable the parent check prior to execution as part of Merge dialog.

Limitations of merge functionality in Dynamics 365.

In Microsoft Dynamics 365 merging feature is great but has some limitations too.

Let’s check some of the important merging limitations you should be aware of.

  • Merging is available only for Account, Contact and Lead system entities.
  • Mering is not available for custom entities yet, there are some suggestion though to Microsoft, to support merging on custom entities.
  • Once records are merged you don’t know which data was merged from subordinate to master record, unless you have enabled auditing on master, secondary and related entities.

Hidden merge tracking fields in Dynamics 365.

After merging, subordinate record shows the notifications similar to the following.

The record was merged with {record name}, and the deactivated.

The notification on the subordinate record provides a link to the master record.

But on master record there is no such link to subordinate record.

So there’s no quick way to navigate to subordinate record from the master record.

How merged notification is displayed on subordinate record?

Entities contain two hidden fields for merge tracking as shown in following screenshot:

  • Merged: Boolean, Shows whether the account has been merged with a master contact.
  • Master ID: Lookup, Unique identifier of the master record.

For internal merge tracking Dynamics 365 updates values of these two fields on subordinate record. These fields are then used to display notification on the subordinate record.

Please note, these fields will not be updated on the master record and because merge is not available on other entities you will not find these fields on entities other than Account, Contact, Lead and Case.

When merge happens, fields on subordinate record will be updated:

Merged: Will be set to true, to indicate this record has been merged with another record.

Master ID: Will be assigned reference to the master record.

Querying merged records

Let’s try now to find the merged records from the advanced find.

As you can see these fields are not available in advanced find, so we cannot filter records to find merged records through advanced find.

These fields are not available even through Add Columns in view, so we cannot see the fields them in grid.

We know if fields are not available through advance find, they may have searchable option set to false in field editor. Let’s check that.

Enable Searchable option on fields

Searchable option on fields is used to show/hide fields from advanced find, if searchable is false then field will not appear in advanced find.

Merged field, searchable is disabled in the classic field editor.

MasterI ID field, searchable is disabled in the classic field editor.

Let’s check through Power Apps portal, as some features are available there.

Merged field

Merged field is editable so checked and saved.

Master ID field

Master ID field is also enabled, so checked and saved.

In Power Apps portal both the fields were editable, so now we should be able to query in advanced find. Let’s check again in advanced find.

Hmmmm….

Merged and MasterId fields are still not available in advanced find, why?

Because Dataverse didn’t saved those fields. Let’s verify.

So, that means we cannot find merged records using views with filter on merged field.

Visualisations options

Since we don’t have views with merged field filter we cannot create visualization for the merged records.

Let’s try to add fields on Form.

In classic editor

We cannot find merged and masteid in classic form editor.

let’s check the modern editor.

You can see fields are not available even in modern forms to add.

Querying Dataverse for Merged and MasterId

Let’s try now to query Dataverse.

Query by FetchXML

Let’s see if we can query the Dataverse using following FetchXML.

FetchXML is filtering records based on merged field.

<fetch top="50" >
  <entity name="contact" >
    <attribute name="fullname" />
    <attribute name="masterid" />
    <attribute name="merged" />
    <filter>
      <condition attribute="merged" operator="eq" value="1" />
    </filter>
  </entity>
</fetch>

Result

Yes, we are able to query through FetchXML, and we can see

Merged is true for subordinate record.

MasterId is set to the Id of the master record.

Note, above result is showing subordinate record only and not the master record. That means merged field is true only on subordinate records.

If you need to find master record, you can do joins on masterid field.

Query by Web API

Let’s try the Web API now.

Xrm.WebApi.online.retrieveMultipleRecords("contact", "?$select=fullname,_masterid_value,merged&$filter=merged eq true").then(
    function success(results) {
        for (var i = 0; i < results.entities.length; i++) {
            var fullname = results.entities[i]["fullname"];
            var _masterid_value = results.entities[i]["_masterid_value"];
            var _masterid_value_formatted = results.entities[i]["_masterid_value@OData.Community.Display.V1.FormattedValue"];
            var _masterid_value_lookuplogicalname = results.entities[i]["_masterid_value@Microsoft.Dynamics.CRM.lookuplogicalname"];
            var merged = results.entities[i]["merged"];
            var merged_formatted = results.entities[i]["merged@OData.Community.Display.V1.FormattedValue"];
        }
    },
    function(error) {
        Xrm.Utility.alertDialog(error.message);
    }
);

Web API Query Result

So we are able to get the results with Web API as well.

Extending merge tracking functionality in Dynamics 365

So far we have learned OOB merge behaviour and some of the limitations of merging in Dynamics 365, let’s try to extend the merging functionality using some customizations.

We will try the following to extend the merging usability:

  • Create view for Merged Contacts by updating view FetchXML and Layout XML.
  • Create visualizations for merged contacts.
  • Adding fields on form by updating FormXML.
  • Subordinate Lookup and Change content tracking on master Contact record.
    • Add custom fields on Contact record.
    • Write plugin on merge to populate Subordinate lookup field on master record.

Create view for Merged Contacts by updating view FetchXML and Layout XML

As we don’t have OOB way of creating view for merged records, let’s try to create by editing FetchXML.

  • Create a View for example Merged Contacts in a solution and export as the unmanaged solution.
  • extract the files and update the FetchXML filter in the customizations.xml file as following.
              <fetch version="1.0" output-format="xml-platform" mapping="logical">
                <entity name="contact">
                  <attribute name="fullname" />
                  <attribute name="masterid" />
                  <attribute name="contactid" />
                  <filter>
                    <condition attribute="merged" operator="eq" value="1" />
                  </filter>
                </entity>
              </fetch>
  • Update Layout XML too to add masterid cell as following.
            <layoutxml>
              <grid name="resultset" jump="fullname" select="1" icon="1" preview="1">
                <row name="result" id="contactid">
                  <cell name="fullname" width="200" />
                  <cell name="masterid" width="200" />
                </row>
              </grid>
            </layoutxml>
  • Zip the files again.
  • Import the solution back in environment and publish.

And you will see the view with merged records.

From view we can observe the following:

  • We have a view now showing merged records, because of our applied filter in view fetchxml.
  • Both Subordinate and Master Lookups are available in same view which enables us to find which records were merged together.

Activating inactive subordinate records

Let’s try to activate the inactive subordinate record in above view and let’s see what happens.

Subordinate record is activated again, let’s go to Merged view.

And the activated record has gone from the view.

Subordinate record is removed from the Merged Contacts view because it’s no longer Subordinate and has following effects on Merged and MasterId fields on activation.

  • Merged field: It is reset back to false.
  • MasterId field: This field is set to null.

Visualizations for merged contacts

Because we have Merged Contacts view available now, we can create nice visualization using that.

Adding fields on form by updating FormXML

I was not able to successfully display Merged and MasterId on forms, If you are able to please share.

Subordinate Lookup and Update Content tracking on master Contact record

As we have already seen subordinate record contains reference to the master record in MasterId field, but there is no reference on the Master record for the Subordinate record.

Let’s do the custom implementation for this.

Add Custom fields on Contact

I have added following two fields on contact, you may try as per your need:

  • Subordinate: Contact Lookup for Subordinate reference.
  • Merge Update Content: String Field to store selected fields content from Subordinate record, to track what values were migrated from subordinate record to the master record.
Write plugin on merge to populate Subordinate field on Master record.

Write and Register following plugin on Post Operation of Merge Message.

using Microsoft.Xrm.Sdk;
using System;
using System.Collections.Generic;
namespace SureshMaurya.Merging.Plugins
{
    public class PostMergeOperation : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            var tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            var organizationServiceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            var organizationService = organizationServiceFactory.CreateOrganizationService(context.UserId);
            var primaryEntityReference = ((EntityReference)context.InputParameters["Target"]);
            var subordinateId = (Guid)context.InputParameters["SubordinateId"];
            var updateContentEntity = (Entity)context.InputParameters["UpdateContent"];
            tracingService.Trace($"Primary Record Id {primaryEntityReference.Id}");
            tracingService.Trace($"Subordinate Record Id {subordinateId}");
            UpdateContentOnPrimaryRecord(organizationService, primaryEntityReference, subordinateId, updateContentEntity);
        }
        private void UpdateContentOnPrimaryRecord(IOrganizationService organizationService, EntityReference primaryEntityReference, Guid subordinateId, Entity updateContentEntity)
        {
            //Prepare Update Content String
            List<string> updateContentCollection = new List<string>();
            foreach (var attribute in updateContentEntity.Attributes)
            {
                updateContentCollection.Add($"{attribute.Key}: {GetAttributeValue(updateContentEntity, attribute.Key)}");
            }
            var updateContentString = string.Join("\n", updateContentCollection);
            //Update Primary Record with Update Content
            Entity entity = new Entity(primaryEntityReference.LogicalName, primaryEntityReference.Id);
            entity["msdc_subordinate"] = new EntityReference() { Id = subordinateId, LogicalName = primaryEntityReference.LogicalName};
            entity["msdc_mergeupdatecontent"] = updateContentString;
            organizationService.Update(entity);
        }
        private string GetAttributeValue(Entity entity, string key)
        {
            if (entity.Attributes.ContainsKey(key))
            {
                if (entity.Attributes[key] is EntityReference)
                {
                    return $"{{Id: {((EntityReference)entity[key]).Id}, Name: {{{((EntityReference)entity[key]).Name}}}}}"; ;
                }
                if (entity.Attributes[key] is OptionSetValue)
                {
                    return ((OptionSetValue)entity[key]).Value.ToString();
                }
                //Handle more field data types.
                return entity[key] as string;
            }
            return string.Empty;
        }
    }
}

When successfully plugin registered try to merge two contact records and select few fields from the subordinate records.

After merging open contact record and observe Subordinate and Merge update Content field. Should be similar to following.

Subordinate Lookup contains reference to the Subordinate record.

Merge Update Content field contains what field data is moved to master from the subordinate record.

Security considerations for Merging in Dynamics 365.

Merging in Dynamics 365 is very powerful but there might be unintended consequences, there are a few security consideration you should keep in mind while merging as explained in Microsoft docs:

Summary

In this blog we learned a lot about merging in Microsoft Dynamics 365. We started with what merging is in Dynamics 365 and what are the different triggers for merging. Then we explored the new enhanced experience for mering in Dynamics 365.

Merging is tracked by two hidden fields in Dynamics 365, we explored what are those two fields and how we can surface then into a view so that those fields are easily accessible and facilitates in creating the visualisation on top of that.

We went ahead and wrote a plugin to keep track of merged record and fields onto the master record.

I hope you liked this blog post about merging and how we can leverage the hidden features of merging, please share your thoughts or anything to improve.

Simplified Show/Hide Ribbon Button in Dynamics 365 based on Asynchronous Operation Result using TypeScript ES6

Recently I had a requirement to show/hide button based on some asynchronous operation result. In your case async operation could be anything such as getting data from some external API, or querying dynamics 365 using Xrm.WebApi.retrieveRecord which returns promise instead of the actual record value.

Toggling ribbon button’s visibility based on asynchronous operation is tricky because before the asynchronous operation completes and result is available function call exits, and true result is never returned and therefore button show/hide doesn’t works as expected.

With a synchronous operation call it works perfectly fine because the function doesn’t exits until we have the required value.

In search of the solution I found a nice article by Andrew Butenko, which actually solved the problem and works fine but the implementation is tricky and not straightforward.

The solution in the linked article has the following high level steps

  • Maintain two flags isAsyncOperationCompleted and isButtonEnabled both initialised to false, and are in outer scope of the function.
  • On page load, function defined on enable rule will be called.
  • Inside the function if isAsyncOperationCompleted is true then return the isButtonEnabled, which will never be the case in first function call.
  • The execution will continue and trigger async operation but will exit the function before the result is available.
  • When the async operation result is available it will set isAsyncOperationCompleted to true and update isButtonEnabled flag based on result, and if isButtonEnabled is true the then call the formContext.ui.refreshRibbon().
  • It will call the enable rule function again and this time because isAsyncOperationCompleted is true in outer scope, correct value of isButtonEnabled will be returned, which was set in previous function call.

Clearly it does the trick but it’s tricky, and there should be a better way of doing this.

And another potential issue could be, in subsequent clicks it will return the same value even if the value changes in the backend.

So, I gave it a try and did the similar thing but in a little different way and improved in following areas:

  • No outer scope level flags.
  • No extra call to formContext.ui.refreshRibbon(), resulting in better performance.
  • Every button click will return the latest value.
  • Easy to follow code logic.
  • Cleaner code.

In the following example I am using TypeScript for better type checking and intellisense support, and using @types/Xrm npm package for Xrm types intellisense.

I have used ES6 feature async/await, which makes it much easier and cleaner to implement the async calls.

namespace Contact {
    export async function ShowCreditButton(formContext: Xrm.FormContext) {
        const accountLookup = formContext.getAttribute<Xrm.Attributes.LookupAttribute>("parentcustomerid").getValue();
        if (accountLookup) {
            const accountId = accountLookup[0].id
            const account = await Xrm.WebApi.retrieveRecord("account", accountId, "?$select=creditonhold");
            return account.creditonhold != true;
        }
        return false;
    }
}

Clearly the above example is much concise and cleaner to implement asynchronous operation in enable rule functions of ribbon button.

If you know a better way of solving this problem, please do share. It’s always good to learn better ways of solving the problems.

Update

There is Microsoft documentation exists for handling asynchronous calls in enable rules using promises. Thanks to Andrew Butenko for sharing the link in comment.

If you want to understand this in plain JavaScript you can refer below, you just have to return promises and it will be handled by the platform.

// Old synchronous style
/*
function EnableRule() {
   const request = new XMLHttpRequest();
   request.open('GET', '/bar/foo', false);
   request.send(null);
   return request.status === 200 && request.responseText === "true";
}
*/

// New asynchronous style
function EnableRule() {
   const request = new XMLHttpRequest();
   request.open('GET', '/bar/foo');

   return new Promise(function(resolve, reject) {
       request.onload = function (e) {
           if (request.readyState === 4) {
               if (request.status === 200) {
                   resolve(request.responseText === "true");
               } else {
                   reject(request.statusText);
               }
           }
       };
       request.onerror = function (e) {
           reject(request.statusText);
       };

       request.send(null);
   });
}

Few points to note about the asynchronous calls in enable rule:

  • Async calls on enable rule are supported in Unified Interface only and not in the classic web clients.
  • There is time limit of 10 seconds, if promise not resolved in 10 seconds then false will be returned.

Summary

In this blog post we learned how we can toggle ribbon button’s visibility based on result of asynchronous operation call defined on enable rule of the ribbon button.

We also learned how we can use the latest ES6 features such as async/await in TypeScript/JavaScript to write much cleaner and concise code.

Quick Reference – Common Microsoft Power Platform CLI Commands for the Development of PCF Components

To create a PCF component we need to work with multiple Microsoft Power Platform CLI (earlier Power Apps CLI) commands.

This blog lists the common Power Platform CLI commands for quick reference which you may find helpful while working with Power Apps Component Framework.

Following are the high level steps in the development life cycle of PCF control.

  • Installing Power Platform CLI
  • Create a new PCF component project
  • Update node packages
  • Build PCF Project
  • Testing / Debugging PCF component
  • Package PCF code components
    • Create solution project
    • Add PCF component reference in the solution
    • Build the solution zip file
  • Manage authentication profiles
    • Create an authentication profile
    • Listing all authentication profiles
    • Switch between authentication profiles
    • Get information about selected environment
    • Delete an authentication profile from the system
    • Delete all authentication profiles from the system
  • Publishing PCF solution file to Dataverse

Installing Power Platform CLI

Before you can execute any Power Platform CLI command you need to install the Power Platform CLI tooling, which you can get from here.

Reference: Get Tooling for Power Apps component framework.

If you already have installed Power Platform CLI, you can update it to the latest version using the following command

pac install latest

Once PCF tooling is installed you are ready to use Power Apps CLI commands for creating PCF components.

Create a new PCF component project of field or dataset type

For any new PCF control you need to first create a project for it. Use the following commands to create a new PCF component project.

Create a PCF component project template for field component.

pac pcf init --namespace SampleNamespace --name SampleComponent --template field

Create a PCF component project template for dataset component.

pac pcf init --namespace SampleNamespace --name SampleComponent --template dataset

Update node packages

The newly created PCF component project contains the references for node packages in packages.json file but the packages are not installed yet.

Execute following commands in project root folder to install node packages. Note this is node CLI command not Power Apps CLI command.

npm install

Build PCF project

While working with PCF component you will need to build project for multiple reasons such as to re-generate ManifestTypes.d.ts for strong type references of new properties or testing your component.

Type the following to build your project

npm run pcf-scripts build

Or simply

npm run build

Multiple pcf-scripts commands are listed in package.json file while generating new project.

Following are all of the pcf-scripts commands listed in package.json file

"build": "pcf-scripts build"
"clean": "pcf-scripts clean"
"rebuild": "pcf-scripts rebuild"
"start": "pcf-scripts start"

Testing / Debugging PCF Component

When you are ready to test your PCF component you can launch local test harness using the following command. Test harness is handy when you want to test or troubleshoot your code component locally.

npm start

Package PCF code components

When PCF component is ready to be deployed, it needs be packaged into a solution zip file, which then can be imported into Dataverse.

Following are the steps to create a solution zip file from the code component

  • Create a solution project
  • Add reference to the PCF component in the solution
  • Build solution zip file

Create solution project

pac solution init --publisher-name developer --publisher-prefix dev

Add PCF component reference in the solution

pac solution add-reference --path c:\users\SampleComponent

Build the solution zip file

Restore packages

msbuild /t:restore

Build unmanaged solution

msbuild

Build managed release solution

msbuild /p:configuration=Release

Manage authentication profiles

Before you can import solution file using Power Apps CLI, you need to have a authentication profile on your system which points to the target Dataverse environment.

All of the authentication profiles gets saved in the authprofiles.json at following location.

C:\Users\{username}\AppData\Local\Microsoft\PowerAppsCLI\authprofiles.json

This files gets created with the first authentication profile creation and gets deleted with the delete of last authentication profile.

Authentication profiles lets you authenticate and import solutions through the command line. You can use the following commands to manage the authentication profiles on your system.

Create an authentication profile

Use the following command to create a new authentication profile on your system.

pac auth create --url https://{org}.crm.dynamics.com

Listing all authentication profiles

If you need to list all of the authentication profiles available on your system, you can use the following.

pac auth list

Switch between authentication profiles

If you have multiple authentication profiles on your system you may need to switch between profiles. You can switch using the following command.

pac auth select --index <index of the active profile>

Get information about selected environment

If you want to get information about selected authentication profile, use following.

pac org who

Delete an authentication profile from the system

If you want to delete an authentication profile from your system, you can delete using the following command. If this is the last authentication profile on your system then it will also delete authprofiles.json from your system.

pac auth delete --index <index of the profile>

Delete all authentication profiles from the system

If you want to delete all of the authentication profiles from your system, you can delete using the following.

pac auth clear

Publishing PCF solution file to Dataverse

Finally we are ready to publish PCF component solution zip file.

Use following command to publish zipped solution file.

pac pcf push --publisher-prefix dev

Summary

In this blog we went through all the commonly used Microsoft Power Platform CLI commands for the development, testing and publishing of PCF components.