Multiple Active Business Process Flow Instances for a record in Dynamics 365 / Dataverse


We recently worked on a requirement where we had to sync Business Process Flow (BPF) data between two different Dataverse environments for the Case (Incident) table. At first glance, the requirement looked straightforward — pick the active BPF instance and replicate it. However, while analyzing the source environment, we encountered an interesting and unexpected scenario.

For certain Case records, we found that there were multiple active Business Process Flow instances (of the same BPF) present for the same record.

Below is an example where we observed two active BPF instances for the same Case record:

Below is our Case record with Research as the active stage.

As we know, Dataverse is designed to maintain only one active BPF instance per record. So naturally, this raised a question — which one should we consider during synchronization?

On further analysis, we observed a consistent pattern. One BPF instance was typically created at the time when the Case record itself was created. The second instance — usually the one with the most recent Modified On value — corresponded to the latest process applied.

Based on this observation, we decided to use the following approach during synchronization:

For records with multiple active BPF instances, we pick the instance with the most recent Modified On value and ignore the older ones. This ensures that we are syncing the most relevant and currently active business process state.

Naturally, we wanted to understand how such a scenario could even exist, so we tried to replicate it. We attempted to create another BPF instance programmatically using the SDK. The code executed successfully and even returned a GUID; however, interestingly, the GUID was always the same as the already existing active instance.

This behavior clearly indicates that the platform prevents creating duplicate active instances through standard SDK operations.

After further experimentation, we discovered that the only way we were able to create multiple active BPF instances was by directly updating the Incident lookup (incidentid) on an existing BPF instance record. By reassigning the BPF instance from one Case record to another, we effectively bypassed the normal BPF lifecycle validations. This resulted in multiple active BPF instances being associated with the same Case record.

During data migration or synchronization scenarios, it is important to handle such anomalies carefully. In our case, choosing the BPF instance with the latest Modified On value ensured that we always picked the most relevant process state.

Hope it helps..

Advertisements
Advertisements

Using addNotification to Simulate Dynamic Tooltips (Dataverse / Dynamics 365)


When working with forms in Dynamics 365 / Power Apps model-driven apps, we often customize field labels based on context, using the setLabel method. At times, we would also like to change the tool tip to go with the changed label of the field. The tooltip is defined as a Description of the field.

Below is the Topic (subject) field of the lead.

A screenshot of a computer

AI-generated content may be incorrect.

However, we cannot set the tool tip (description) of the field dynamically in the form using the Client API. So, what do we do when the meaning of a field changes depending on another value on the form? That’s where addNotification comes in as a handy workaround.

Let us take a simple example to see how we can use it. On the Lead form, the Topic(subject) field means different things depending on the Lead Source. So here we will be changing the label of the Topic (subject) field, along with setting a different notification message.

For e.g., if Lead Source – Advertisement, we are changing the label to Campaign Name. We can also notice the bulb icon next to the field.

A screenshot of a computer

AI-generated content may be incorrect.

Clicking on it, we can see our message –

A screenshot of a web page

AI-generated content may be incorrect.

Similarly, on changing the Lead Source to Web, we are changing the label to Landing Page, and clicking on the icon, we can see a different message.

A screenshot of a web page

AI-generated content may be incorrect.

Sample Code –

function updateSubjectField(executionContext) {
	
    var formContext = executionContext.getFormContext();
    var leadSourceAttr = formContext.getAttribute("leadsourcecode");
    var subjectControl = formContext.getControl("subject");
   
    subjectControl.clearNotification("subjectTooltip");

    var leadSource = leadSourceAttr ? leadSourceAttr.getValue() : null;

    if (leadSource === 1) { 
        // 1 = Advertisement
        subjectControl.setLabel("Campaign Name");
        subjectControl.addNotification({
            messages: ["Enter the name of the ad campaign"],
            notificationLevel: "RECOMMENDATION",
            uniqueId: "subjectTooltip"
        });
    }
    else if (leadSource === 2) { 
        // 2 = Referral
        subjectControl.setLabel("Referrer Notes");
        subjectControl.addNotification({
            messages: ["Mention details about the referrer"],
            notificationLevel: "RECOMMENDATION",
            uniqueId: "subjectTooltip"
        });
    }
    else if (leadSource === 8) { 
        // 3 = Web
        subjectControl.setLabel("Landing Page");
        subjectControl.addNotification({
            messages: ["Provide the landing page URL"],
            notificationLevel: "RECOMMENDATION",
            uniqueId: "subjectTooltip"
        });
    }
    else {
        // Default
        subjectControl.setLabel("Subject");
    }
}

While addNotification isn’t a perfect replacement for a native tooltip, it’s a practical workaround when we need dynamic, context-aware user guidance.

Hope it helps..

Advertisements

Using gridContext.refreshRibbon() to Dynamically Show/Hide a Subgrid Ribbon Button – Dynamics 365 / Dataverse


In Dynamics 365 / Dataverse, sometimes we want to show or hide a ribbon button based on a form field value. But when the button is on a subgrid, it does not refresh automatically when a field changes on the form. We can handle this requirement using gridContext.refreshRibbon(). It is a small but very useful method that helps to refresh the subgrid ribbon without saving or reloading the form.

Here we are taking a simple scenario to understand the usage.

We will only show the New Case button on the Case Subgrid if the Preferred Method of Contact = Any else we will hide it.

A screenshot of a computer

AI-generated content may be incorrect.

Below is our JavaScript function to check the field value and return true or false. This function will be used as CustomRule for our Add New button command’s EnableRule on the subgrid. This function checks if the Preferred Method of Contact is ‘Any’. If yes, it returns true. Otherwise, it returns false.

A screen shot of a computer code

AI-generated content may be incorrect.

We are passing CRM Parameter = PrimaryControl here.

Depending on where the button lives (Form ribbon or Subgrid ribbon), the correct context is passed.

  • On a form: PrimaryControl is the formContext.
  • On a Subgrid: PrimaryControl gives the context of the parent form hosting the subgrid.

Below we have customized the Add New Subgrid button for Case and added a new Enable Rule for its command.

A screenshot of a computer

AI-generated content may be incorrect.

Now on the form load, the Add New button on the subgrid will be hidden on the form load event.

But when we change the value for the Preferred Method of Contact we will not see any effect on the Add New button. For it to work we need to use the refershRibbon method of the grid’s context as shown below.

We added it on the onChange event for the Preferred Method of Contact field so that when a user changes it, the subgrid ribbon refreshes.

A screen shot of a computer code

AI-generated content may be incorrect.

Now, when a user changes the Preferred Method of Contact, the subgrid ribbon will refresh and check again if the button should be visible.

As a result, now the Add New button appears on the Case subgrid when the Preferred Method of Contact is ‘Any’.

A screenshot of a computer

AI-generated content may be incorrect.

The ribbon refreshes immediately when the field changes to Email or any other value except Any.No need to save or reload the form.

A screenshot of a computer

AI-generated content may be incorrect.

JavaScript –

function showAddNewButtonOnCaseSubgrid(primaryControl) {
    var formContext = primaryControl;
    var preferredMethod = formContext.getAttribute("preferredcontactmethodcode");    
    var preferredMethodValue = preferredMethod.getValue();
    // Check if Preferred Method is 'Any' 
    if (preferredMethodValue === 1) {
        return true;
    }
    else {
        return false;
    }
}
function refreshCaseSubgridRibbon(executionContext) {
    var formContext = executionContext.getFormContext();
    var gridContext = formContext.getControl("Subgrid_Cases");  
    if (gridContext) {
        gridContext.refreshRibbon();  
    }
}

The helpful post – https://butenko.pro/2019/04/16/refreshing-the-ribbon-form-vs-grid/

Hope it helps..

Advertisements

Using External Value property of Choice / Option Set Field for Integration – Dynamics 365 / Dataverse


When working with Choice fields (Option Sets) in Dataverse, we mostly use the label and internal numeric value. But there’s also a lesser-known property — External Value — which can be quite handy, especially in integration scenarios.

Below is the Priority column of the case table.

A screenshot of a computer

AI-generated content may be incorrect.

We have Severe, Moderate, and Minor external values specified for High, Normal, and Low choices respectively. These external values could be how the external third-party system is tracking the priority values to which we are integrating.

The external system sends us “priority”:” high” and we map that to the internal value 1 in Dataverse.

While sending data to the external system we convert the internal value to the external code i.e. 1 to. High

Below is how we can read the external values.

     var myServiceClient = new CrmServiceClient(connectionString);     

        if (myServiceClient.IsReady)
        {
            var request = new RetrieveAttributeRequest
            {
                EntityLogicalName = "incident",
                LogicalName = "prioritycode",
                RetrieveAsIfPublished = true
            };

            var response = (RetrieveAttributeResponse)myServiceClient.Execute(request);
            var picklistMetadata = (PicklistAttributeMetadata)response.AttributeMetadata;

            foreach (var option in picklistMetadata.OptionSet.Options)
            {
                Console.WriteLine($"Value: {option.Value}, Label: {option.Label?.UserLocalizedLabel?.Label}, External: {option.ExternalValue}");
            }

            string ext = "Severe";
            var match = picklistMetadata.OptionSet.Options.FirstOrDefault(o => o.ExternalValue == ext);
            if (match != null)
            {
                Console.WriteLine($"\nExternal '{ext}' maps to Value {match.Value}");
            }

        }
A screenshot of a computer

AI-generated content may be incorrect.

Using External values, we can decouple integrations from the internal values that we use for our choice column. By using them, we can speak the language of the external system while still maintaining proper structure and metadata within Dataverse.

Hope it helps..

Advertisements

Querying / Filtering MultiSelect Choice / OptionSet Fields in Dataverse / Dynamics 365


MultiSelect OptionSet (Choices) fields in Dataverse provide a flexible way to store multiple values within a single field. However, querying and filtering these fields require different techniques depending on the approach used.

In this blog post, we will explore various ways to filter records based on the Skills field (cr1a7_skills), which has the following values:

Name

Value

C#

255780000

Java

255780001

Python

255780002

We have the Skills (choices) field in our Contact table.

The query is to fetch all the contact records where skills includes C# or Java.

Filtering Using FetchXML

FetchXML allows filtering MultiSelect Option Set fields using the contain-values operator.

<fetch>
  <entity name="contact">
    <attribute name="fullname" />    
    <filter>
      <condition attribute="cr1a7_skills" operator="contain-values">
        <value>255780000</value>
        <value>255780001</value>
      </condition>
    </filter>
  </entity>
</fetch>

Filtering Using QueryExpression (C#)

Using QueryExpression, we can apply the ContainValues condition to filter Multi

var query = new QueryExpression("contact");
query.ColumnSet.AddColumns("fullname");
query.Criteria.AddCondition("cr1a7_skills", ConditionOperator.ContainValues, 255780000, 255780001);

Filtering Using OData (Web API)

OData allows filtering MultiSelect Option Set fields using the ContainValues function.

https://orgname.crm.dynamics.com/api/data/v9.2/contacts?$select=fullname,createdon,modifiedon,statecode&$filter=(Microsoft.Dynamics.CRM.ContainValues(PropertyName='cr1a7_skills',PropertyValues=['255780000','255780001']))

Filtering Using SQL4CDS (XrmToolBox)

If you’re using the SQL4CDS tool in XrmToolBox, you can filter MultiSelect Option Set fields using LIKE conditions.

SELECT contactid,
       fullname
FROM   contact
WHERE  statecode = 0
       AND (cr1a7_skills = '255780000'
            OR cr1a7_skills LIKE '255780000,%'
            OR cr1a7_skills LIKE '%,255780000,%'
            OR cr1a7_skills LIKE '%,255780000'
            OR cr1a7_skills = '255780001'
            OR cr1a7_skills LIKE '255780001,%'
            OR cr1a7_skills LIKE '%,255780001,%'
            OR cr1a7_skills LIKE '%,255780001'            
            );

Filtering Using LINQ (C#)

For LINQ queries, MultiSelect Option Sets must be processed as OptionSetValueCollection.


        if (myServiceClient.IsReady)
        {
            using (var context = new OrganizationServiceContext(myServiceClient))
            {

                var skillValues = new List<int> { 255780000, 255780001 };              
                              
                var allContacts = context.CreateQuery("contact")
                .Where(c => c["cr1a7_skills"] != null &&
                            (int)c["statecode"] == 0)
                .ToList();                
              
                var filteredContacts = allContacts
                    .Where(c => ((OptionSetValueCollection)c["cr1a7_skills"])
                                .Select(osv => osv.Value)
                                .Any(skill => skillValues.Contains(skill)))
                    .Select(c => new
                    {
                        ContactId = c["contactid"],
                        FullName = c.Contains("fullname") ? c["fullname"].ToString() : string.Empty
                    })
                    .ToList();

                var result = filteredContacts;

            }
        }

Get more detailed information

Hope it helps..

Customizing Business Process Flows: Stage Validation Using JavaScript (Dynamics 365 / Dataverse)


Business Process Flows (BPF) in Dynamics 365 offer a structured way to guide users through a defined process. However, there are scenarios where progression to the next stage must be validated against specific business rules. In this blog, we see how to implement custom validations on stage progression using JavaScript.

Let us take a simple scenario where a Lead can only progress to the next stage of a BPF if

Lead Quality = Hot and Lead Source = Web

If these conditions are not met, users will receive a notification, and the stage change will be prevented.

Below is the sample code


function OnLoad(executionContext)
{
 var formContext = executionContext.getFormContext();
 formContext.data.process.addOnPreStageChange(validateStageProgression);
}


function validateStageProgression(executionContext)
{
	    var bpfSampleStage = "6e2b5d9e-da30-4a47-8ca9-d75c24fd51f4";
        var formContext = executionContext.getFormContext();
		var rating = formContext.getControl('header_process_leadqualitycode').getAttribute().getValue();
		var leadSource = formContext.getControl('header_process_leadsourcecode').getAttribute().getValue();
		var stageObj = formContext.data.process.getActiveStage();
        var stageId = stageObj.getId();
		var requiredFieldErrorId = "contractValidationNotificationId";
        formContext.ui.clearFormNotification(requiredFieldErrorId);
		
		if(stageId)
		{
			 if (stageId.toLowerCase() === bpfSampleStage)
			 {
				  if (executionContext.getEventArgs().getDirection() === "Next") 				  
				{
                    executionContext.getEventArgs().preventDefault();
					if(rating == 1 && leadSource == 8) // rating = Hot and Source = Web
					{
						formContext.data.process.removeOnPreStageChange(validateStageProgression);
                        formContext.data.process.moveNext();
					}	
					else
					{
						notificationMessage = "Cannot move to the next stage until conditions are met !";
						formContext.ui.setFormNotification(notificationMessage, "ERROR", requiredFieldErrorId)
					}
				}
			 }
		}
}

The addOnPreStageChange method registers the validation function on the form’s load event to monitor stage changes.

The preventDefault() method stops the stage transition if the conditions are not met, ensuring data integrity.

If the validation fails, an error notification is displayed using the setFormNotification() method, guiding users to correct the data.

Upon satisfying the conditions, moveNext() is invoked programmatically to move the process to the next stage.

As shown below, clicking on Next as rating and lead source values do not satisfy the condition, we can see the notification on the form and the user is not able to move to the next stage.

Also check – https://nishantrana.me/2024/10/09/use-javascript-to-enable-a-field-only-if-the-stage-is-active-in-business-process-flow-dynamics-365-dataverse/

https://nishantrana.me/2024/10/10/use-javascript-to-add-onchange-event-to-a-field-in-the-business-process-flow/

https://nishantrana.me/2024/10/23/javascript-for-fields-in-business-process-flow-few-key-points-dataverse-dynamics-365/

Hope it helps..

Advertisements