“Transaction not started. There is no active transaction” error (Dynamics 365 / Dataverse)


We recently ran into an interesting and slightly frustrating issue while trying to mark an activity (Appointment/Phone Call/ Task) as Completed in Dynamics 365.

Whenever we tried to mark the activity as completed, we were getting the following error. We were only getting the exception when the activity was owned by a Team instead of a User.

Error Code: 0x80040251 Message: “There is no active transaction. This error is usually caused by custom plug-ins that ignore errors from service calls and continue processing.”

At this point, we were quite confident that this was related to some custom logic interfering with the transaction pipeline. The error message itself clearly hinted towards plug-ins swallowing exceptions and continuing execution.

So, we started with the usual debugging checklist:

Checked all synchronous plug-ins on Activity, Appointment, and related entities. Disabled custom plug-ins one by one. Looked into ownership/team-related logic. Assigned System Admin role to the Team, etc.

Surprisingly, even after disabling all plug-ins, the issue was still occurring. That was our first big clue that something else was at play.

After wasting good enough time, we shifted our attention towards workflows and custom workflow activities. And that’s where things got interesting.

We found a custom workflow activity that had a try-catch block implemented like this:

The exception was being caught… but not thrown again. Essentially, the workflow activity was swallowing the exception silently and allowing execution to continue.

This behavior breaks the transaction pipeline. Dynamics expects failures to bubble up properly so that the transaction can be rolled back. When exceptions are consumed like this, the platform ends up in an inconsistent state, which is why we see errors like ‘There is no active transaction’.

We started first by updating the code to rethrow the exception, that’s where we realized the actual error – which was a SystemUser record does not exist. Basically in our code we were assigning Team Guid’s to a lookup of type System User causing this issue.

After getting to know the exact issue, we updated our logic accordingly to fix the issue.

Key takeaway from this experience:

We should never suppress exceptions in plug-ins or custom workflow activities without proper handling. If something fails in the pipeline, it is better to let it fail cleanly rather than leaving the system in a broken transactional state.

Hope it helps..

Advertisements

Plugin Registration Tool Login with Multi-Factor Authentication (MFA) – Uncheck “Show Advanced”


If we’re logging into the Plugin Registration Tool using an account protected with Multi-Factor Authentication (MFA), there’s one small setting that can cause login failures — Show Advanced.

We need to make sure “Show Advanced” is unchecked before clicking Login. When this option is selected, the tool exposes legacy Username and Password fields, which do not support modern Azure AD MFA authentication. Leaving it unchecked forces the tool to open the modern Microsoft login prompt, where we can complete your MFA challenge successfully.

Clicking on Login.

Microsoft also mentions this behavior in the official documentation under the plugin registration tutorial.

Reference:

Microsoft Learn – Register a plug-in

https://learn.microsoft.com/en-us/power-apps/developer/data-platform/tutorial-write-plug-in#register-plug-in

Hope it helps..

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

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

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