Featured

Recent Posts


Something went wrong. Please refresh the page and/or try again.

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

Renaming Sitemap Display Name in Dataverse / Dynamics 365


While working with a model-driven app in Dataverse, we needed to change the display name of the sitemap. What made this interesting was that there is no option in the UI to rename the sitemap display name directly.

After exploring the UI options and confirming that the sitemap display name cannot be updated there, the only approach that worked was a solution-level change. The solution was to export the solution that contained the sitemap, update the sitemap display name in customizations.xml, and then import and publish the solution again. We exported the solution as unmanaged and extracted the ZIP file. Inside the extracted files, we opened customizations.xml. This file contains the full definition of the app’s sitemap, including its localized display name. Within the XML, the sitemap definition appears under the AppModuleSiteMaps section. A simplified version of the relevant structure looks like this:

The key part here is the LocalizedNames node. This is where the sitemap display name is defined. To rename the sitemap, we updated the value of the description attribute for the required language code.

After making this change, we repackaged the solution, imported it back into the environment, and published the customizations. Once the import was completed / published, the sitemap display name reflected the new value everywhere and, importantly, the change persisted.

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

Advancing and Finishing a BPF Using RetrieveProcessInstancesRequest and RetrieveActivePathRequest (Dataverse / Dynamics 365)


In earlier posts, we looked at how to move a Business Process Flow (BPF) stage and finish the process by directly updating the BPF entity instance. In this post, we’ll use RetrieveProcessInstancesRequest and RetrieveActivePathRequest to move a Business Process Flow to its final stage and finish it, using the Phone to Case Process as a reference.

A Business Process Flow defines not just stages, but also valid paths through those stages. Instead of assuming which stage comes next, we can ask Dataverse for the active path of the current process instance. This makes the logic more resilient to future changes, such as reordering stages or adding conditional paths.

By using the active path:

  • We avoid hard-coding stage IDs
  • We ensure the stage we move is to valid for the process
  • We can construct a correct traversed path directly from platform metadata.

Step 1: Retrieve the Active BPF Instance

The first step is to identify the active Business Process Flow instance for the Case. We do this using RetrieveProcessInstancesRequest, which returns all BPF instances associated with the record, ordered based on modifiedon. The most recently modified bpf instance will be the one active in the record.

RetrieveProcessInstancesRequest processInstanceRequest =
    new RetrieveProcessInstancesRequest
    {
        EntityId = caseId,
        EntityLogicalName = "incident"
    };

RetrieveProcessInstancesResponse processInstanceResponse =
    (RetrieveProcessInstancesResponse)orgService.Execute(processInstanceRequest);

Step 2: Retrieve the Active Path and Build the Traversed Path

Instead of guessing stage progression, we retrieve the active path using RetrieveActivePathRequest.

RetrieveActivePathRequest pathReq =
    new RetrieveActivePathRequest { ProcessInstanceId = processInstanceId };

RetrieveActivePathResponse pathResp =
    (RetrieveActivePathResponse)orgService.Execute(pathReq);

The response contains all stages in the active path, in the correct order. From this data, we build the complete traversed path by iterating through every stage returned and collecting their processstageid values.

var traversedPathList = new List<string>();
foreach (var stage in pathResp.ProcessStages.Entities)
{
    traversedPathList.Add(
        ((Guid)stage.Attributes["processstageid"]).ToString()
    );
}

string completeTraversedPath = string.Join(",", traversedPathList);

This approach guarantees that the traversedpath reflects the exact path defined by the process configuration, rather than a manually constructed sequence.

Step 3: Move the BPF to the Final Stage

We then get the last stage’s GUID and use it to update activestageid of the BPF instance along with the traversed path.

Guid finalStageId =
    (Guid)pathResp.ProcessStages.Entities.Last()
        .Attributes["processstageid"];

var updateBpf = new Entity("phonetocaseprocess", processInstanceId)
{
    ["activestageid"] = new EntityReference("processstage", finalStageId),
    ["traversedpath"] = completeTraversedPath
};

orgService.Update(updateBpf);

Step 4: Finish (Deactivate) the Business Process Flow

Reaching the final stage does not automatically complete the process. To mirror the Finish button in the UI, we explicitly mark the BPF instance as inactive and finished.

var finish = new Entity("phonetocaseprocess", processInstanceId)
{
    ["statecode"] = new OptionSetValue(1),   // Inactive
    ["statuscode"] = new OptionSetValue(-1)  // Finished
};

orgService.Update(finish);

After this update, the Business Process Flow appears as Completed in the UI and can no longer be progressed.

Helper method –

public static void MoveBpfToFinalStageAndFinish(
    IOrganizationService service,
    Guid primaryRecordId,
    string primaryEntityLogicalName,
    string bpfSchemaName,
    string expectedBpfName)
{
    // Step 1: Retrieve active BPF instance
    var processInstanceRequest = new RetrieveProcessInstancesRequest
    {
        EntityId = primaryRecordId,
        EntityLogicalName = primaryEntityLogicalName
    };

    var processInstanceResponse =
        (RetrieveProcessInstancesResponse)service.Execute(processInstanceRequest);

    if (!processInstanceResponse.Processes.Entities.Any())
        return;

    var bpfInstance = processInstanceResponse.Processes.Entities.First();
    var processInstanceId = bpfInstance.Id;

    // Optional safety check – ensure correct BPF
    if (!bpfInstance.Attributes.Contains("name") ||
        bpfInstance["name"].ToString() != expectedBpfName)
        return;

    // Step 2: Retrieve active path
    var pathRequest = new RetrieveActivePathRequest
    {
        ProcessInstanceId = processInstanceId
    };

    var pathResponse =
        (RetrieveActivePathResponse)service.Execute(pathRequest);

    if (pathResponse.ProcessStages.Entities.Count == 0)
        return;

    // Step 3: Build traversed path from active path
    var traversedPathList = new List<string>();
    foreach (var stage in pathResponse.ProcessStages.Entities)
    {
        traversedPathList.Add(
            ((Guid)stage["processstageid"]).ToString()
        );
    }

    string traversedPath = string.Join(",", traversedPathList);

    // Final stage = last stage in active path
    Guid finalStageId =
        (Guid)pathResponse.ProcessStages.Entities.Last()["processstageid"];

    // Step 4: Move BPF to final stage
    var updateBpf = new Entity(bpfSchemaName, processInstanceId)
    {
        ["activestageid"] = new EntityReference("processstage", finalStageId),
        ["traversedpath"] = traversedPath
    };

    service.Update(updateBpf);

    // Step 5: Finish (deactivate) the BPF
    var finishBpf = new Entity(bpfSchemaName, processInstanceId)
    {
        ["statecode"] = new OptionSetValue(1),   // Inactive
        ["statuscode"] = new OptionSetValue(2)  // Finished (verify in org)
    };

    service.Update(finishBpf);
}

Get all the details here

Hope it helps..

Advertisements

Nishant Rana's Weblog

Everything related to Microsoft .NET Technology

Skip to content ↓