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

No Dependencies Shown… But still can’t delete the component? Check Your Cloud Flows (Dataverse / Dynamics 365)


Recently, while performing cleanup in one of our environments, we were removing unused components to reduce clutter and technical debt. As part of this activity, we attempted to delete an old Business Process Flow (BPF) that was no longer required.

However, when trying to delete the Business Process Flow, we were greeted with the following error message:

Failed to delete (). Object dependencies exist; please review before deleting.

At first glance, this seemed straightforward — if dependencies exist, we just need to review and remove them. But here’s where things became confusing. When we opened the Show Dependencies option for the Business Process Flow, nothing was listed. No forms, no views, no plugins, no workflows — absolutely nothing.

After searching for different components, we finally found one of the cloud flows referring to it. It was creating an instance of the BPF. That reference was enough for Dataverse to block deletion — even though it wasn’t being displayed in the dependency viewer for the component.

Once we identified the cloud flow, we removed the step that was creating the Business Process Flow instance. After saving and publishing the updated flow, we attempted deletion again.

This time — success.

The Business Process Flow was deleted without any issues.

Hope it helps ..

Advertisements

Solution Failed to Import – Missing Lookup View Dependency in Dataverse / Dynamics 365


Recently, while trying to import a solution, we got the below dependencies error.

Solution ” Configuration” failed to import: The dependent component SavedQuery (Id=50658a7f-473b-ec11-8c64-000d3a8ead20) does not exist. Failure trying to associate it with SystemForm (Id=a00da85e-5fc4-f011-bbd3-000d3ad2506c) as a dependency. Missing dependency lookup type = PrimaryKeyLookup.

The error indicated that a specific Contact view (ASP C1 Contacts) was missing. When we checked the dependencies, it showed that this view had a dependency on the main form of a custom table.

That form contained multiple Contact lookup fields. However, when we reviewed all the lookup configurations, none of them appeared to reference that particular view. Each lookup had its Default View set to “Contacts Lookup View,” and the “Allow users to change view” option was disabled. Everything looked correct in the UI.

Since the issue wasn’t visible from the form editor, we exported the solution and inspected the solution.xml file. There, we could clearly see the missing dependency details, including the GUID of the problematic view.

Using that view GUID ({50658a7f-473b-ec11-8c64-000d3a8ead20}), we searched inside the customizations.xml file. This revealed that the view was still being referenced by one of the lookup controls (display name “Prospect Resident”), even though the form configuration showed a different default view. Essentially, the form XML still contained an old reference to that view.

To resolve the issue, we removed the lookup from the form and added it again. After re-adding it, we temporarily enabled the “Allow users to change view” option, selected a few views, saved and published the form, and then disabled the option again and published once more. This process refreshed the lookup configuration and removed the hidden dependency.

After that, the solution was imported successfully.

This issue highlights how form XML can retain hidden view references even when the UI configuration appears correct. When facing similar “SavedQuery does not exist” errors, inspecting customizations.xml for the view GUID can help quickly identify the root cause.

Hope it helps..

Advertisements

Fixed: Audit History Page Not Loading (Dataverse / Dynamics 365)


Recently, we ran into an issue where the Audit History page stopped loading on the form. Interestingly, the problem was limited only to the Account forms.

Whenever we tried to open Audit History, we received the generic error below:

An error has occurred.

Try this action again. If the problem continues, check the Microsoft Dynamics 365 Community for solutions or contact your organization’s Microsoft Dynamics 365 Administrator. Finally, you can contact Microsoft Support.

A screenshot of a computer

AI-generated content may be incorrect.

To investigate further, we raised a Microsoft Support ticket. After reviewing the issue, Microsoft informed us that the problem was likely related to a custom control used on the Account form. They shared the Form ID (GUID) along with the control classid F9A8A302-114E-466A-B582-6771B2AE0D92, which corresponds to that custom control.

Microsoft asked us to inspect the Form XML of the affected Account form. Specifically, they advised searching for all controls that use the given classid and carefully reviewing the uniqueid property of each control. We were also asked to verify that there were no case mismatches in the GUIDs and that every uniqueid had a matching entry in the controldescription section of the Form XML.

To identify the correct form, we used a SQL4CDS query to retrieve the Form Name and Form ID.

For easier analysis, we created a temporary solution, added the affected Account form to it, exported the solution, and opened the Form XML.

While reviewing the Form XML, we found six instances of the control using the specified classid. For five of these controls, the uniqueid had a corresponding entry in the controldescription section. However, one control was missing this mapping. The problematic uniqueid was 815D8A5B-6355-47B5-9500-EE2D658820D5.

To resolve the issue, we updated this uniqueid to match an existing and valid one already present for the address1_line1 control, which was f9f5f514-a6f9-4e5f-bed9-e53516880ede. After making the change, we zipped the solution, imported it back into the environment, and published the updates.

More on that Address Input Control – https://www.axazure.com/en/how-to-use-the-new-address-input-control-in-model-driven-app

Once the solution was re-imported, the Audit History page started working correctly for Account forms, confirming that the issue was resolved.

This could be helpful if you run into a similar Audit History issue caused by custom controls and Form XML inconsistencies.

Hope it helps..

Advertisements

Why We Switched Our Plugin from PreOperation to PreValidation – Dataverse / Dynamics 365


We had a business requirement to block the closing of a Quote as Lost under certain conditions. Instead of leaving the quote in an Active state, we wanted the system to explicitly move it back to Draft and show a clear error message to the user explaining why the close action was not allowed.

We initially registered our plugin on the Close message in the PreOperation stage. The logic was simple: detect the Lost status, set the quote back to Draft, and throw an exception to cancel the close operation.

The plugin executed exactly as expected. However, the result was not what we intended. Although the quote closure was blocked, the quote never moved to Draft. This happened because PreOperation runs inside the same transaction as the Close message. When we threw an InvalidPluginExecutionException, Dataverse rolled back everything in that transaction, including our SetStateRequest.

To fix this, we moved the same plugin logic to the PreValidation stage, and the behavior immediately changed. PreValidation runs outside the main transaction, before Dataverse starts processing the Close request. This allowed us to:

  • Update the quote state to Draft
  • Throw an exception to cancel the Close
  • Keep the quote in Draft without rollback

Sample Code for reference –

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;

public class BlockQuoteCloseAndRevertToDraft : IPlugin
{
    public void Execute(IServiceProvider serviceProvider)
    {
        var context =
            (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

        if (!context.InputParameters.Contains("QuoteClose") ||
            !context.InputParameters.Contains("Status"))
            return;

        var status = (OptionSetValue)context.InputParameters["Status"];

        // Custom Lost status value
        const int LostStatusValue = 100000001;

        if (status.Value != LostStatusValue)
            return;

       // other business logic / check, return if not valid else continue and throw exception 

        var quoteClose = (Entity)context.InputParameters["QuoteClose"];
        var quoteRef = quoteClose.GetAttributeValue<EntityReference>("quoteid");

        if (quoteRef == null)
            return;

        var serviceFactory =
            (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
        var service = serviceFactory.CreateOrganizationService(context.UserId);

        service.Execute(new SetStateRequest
        {
            EntityMoniker = new EntityReference("quote", quoteRef.Id),
            State = new OptionSetValue(0),   // Draft
            Status = new OptionSetValue(1)   // Draft status
        });

        throw new InvalidPluginExecutionException(
            "This quote cannot be closed at this stage and has been reverted to Draft."
        );
    }
}

Hope it helps..

Advertisements

Using the Restore Message to Recover Deleted Records in Dataverse


Accidental data deletion in Dataverse happens more often than we expect. A bulk delete job, an incorrect Power Automate flow, or incorrect manual delete can remove important records in seconds. Instead of restoring the environment, Dataverse provides a much better alternative: the Restore message, which allows us to recover deleted records programmatically.

Prerequisite: Recycle Bin Must Be Enabled

The Restore message works only if the Recycle Bin is enabled for the table before the record is deleted. If the recycle bin is disabled, deleted records are permanently removed and cannot be recovered using the SDK.

When a record is deleted, Dataverse moves it to the recycle bin. We can query these deleted records using RetrieveMultiple with DataSource = “bin” and then pass the deleted record’s ID to the Restore message. The restore operation recreates the record using the same record ID.

Sample Console App

The following console app retrieves deleted Case (incident) records from the recycle bin and restores them using the Restore message.

static void Main(string[] args)
{
    Console.WriteLine("Restore Deleted Records started.");

    string connString = @"AuthType=OAuth;
Username=your.user@tenant.onmicrosoft.com;
Password=********;
Url=https://yourorg.crm.dynamics.com/;
AppId=00000000-0000-0000-0000-000000000000;
RedirectUri=app://58145b91-0c36-4500-8554-080854f2ac97/";

    var serviceClient = new CrmServiceClient(connString);
    var service = serviceClient.OrganizationWebProxyClient
        ?? throw new Exception("Organization service not available");

    // Retrieve deleted records from recycle bin
    QueryExpression query = new QueryExpression("incident")
    {
        ColumnSet = new ColumnSet("title"),
        DataSource = "bin",
        TopCount = 50
    };

    var deletedCases = service.RetrieveMultiple(query);

    if (deletedCases.Entities.Count == 0)
    {
        Console.WriteLine("No deleted records found.");
        return;
    }

    foreach (var deletedCase in deletedCases.Entities)
    {
        Guid caseId = deletedCase.Id;
        string title = deletedCase.GetAttributeValue<string>("title") ?? "Case";

        // Prepare entity for restore
        Entity caseToRestore = new Entity("incident", caseId);
        caseToRestore["title"] = title + " (Restored)";

        // Restore using Restore message
        OrganizationRequest restoreRequest = new OrganizationRequest("Restore");
        restoreRequest["Target"] = caseToRestore;

        service.Execute(restoreRequest);

        Console.WriteLine($"Restored record: {caseId}");
    }

    Console.WriteLine("Deleted records restored successfully.");
}

E.g., we have the following deleted case, and its related records in our recycle bin.

On running our console app, we can see our deleted case records restored.

Get all the details here.

Hope it helps..

Advertisements