For those who are well versed in rolling out solution updates within Dynamics CRM/365 for Enterprise (CRM/D365E), the process will always have a certain familiarity to it, with a few surprises rolled in now and again. Often, the update will proceed as anticipated; sometimes, you may encounter bizarre issues. I can remember a particularly strange incident I had last year where a solution import would get to about 90-95% completion…and then the green progress bar would suddenly start rolling back to nothing. The import progress window would then hang with no further guidance or error message. To try and determine the root cause, we had to interrogate the importjob entity within the system, which ended up showing the import job progress stuck at 0.15846281% : / In the end, we had to escalate the issue to Microsoft for further investigation, but rest assured that if you have not yet witnessed your own curious solution import experience, it’s definitely in the post 🙂

Thankfully, if you are new to the whole “rolling out solution update” thing, you can be assured that the process is relatively straightforward, and mostly without issue. If you have been handed a set of solution import instructions for the first time, though, you may be wondering why something similar to the following step is included:

Go into the Data Management -> Duplicate Detection Rules page and click Publish on all Duplicate Detection Rules that have a Status Reason of Unpublished

Unfortunately, after importing a solution update, CRM/D365E will automatically unpublish all of your Duplicate Detection Rules automatically. You are therefore required to explicitly publish them again, lest you start to encounter a sudden increase in duplicate records and database storage within your system. The reason why this happens is both understandable and frustrating in equal measure. As outlined in the following MSDN article on the subject:

A duplicate rule condition specifies the name of a base attribute and the name of a matching attribute. For example, specify an account as a base entity and a contact as a matching entity to compare last names and addresses

As part of the above, explicit matchcodes are created for every record that the Duplicate Detection Rule is targeting, based on the current metadata of your CRM/D365E entities and attributes. Because your solution update can potentially alter significant aspects of this metadata, the system automatically unpublishes all Duplicate Detection Rules as a precaution.

The above is perhaps trivial in nature, as the actual process of re-publishing all Duplicate Detection Rules is somewhat negligible in effort terms. Where difficulties can arise is if someone innocently overlooks this part of the process or if your system has many different Duplicate Detection Rules, in a mixture of Unpublished/Published state. You would have to specifically make a note of which rules were Published before beginning your solution import so that you can ensure that the correct rules are published after the fact. I would have thought that after so many versions of the product, that something would be added to address this – for example, perhaps a checkbox at the start of the Solution Import Wizard that lets you specify whether all currently published rules should be reactivated after the import completes successfully.

If you find that the above is an annoyance that you can do without no longer, like with many things on the platform, there is a solution that can be deployed in code. The SDK exposes the PublishDuplicateRuleRequest class, which does exactly what it says on the tin – meaning that you can write a plugin that applies this functionality accordingly. The tricky bit comes in determining which Message (i.e. action) on the platform that you wish to run this against. CRM/D365E does not expose a SolutionUpdate or SolutionImport message that we can piggy-back onto, so we have to look at the PublishAll message instead – the action that is triggered when you press Publish All Customizations in the system. This is because this is generally the action you will always need to take when importing an (unmanaged) solution. As a result, we can write a plugin class that is triggered on the Post-Operation event of this entity to automatically publish all Unpublished Duplicate Detection Rules in the system!

The snippet below is adapted from the sample code provided by Microsoft, but has been tweaked as follows:

  • A QueryExpression is used as opposed to QueryByAttribute, since we need to query on two separate attributes and their values – statecode and statuscode. You also cannot return an easily accessible count on all results returned with QueryByAttribute. We will see why is useful in a few moments.
  • The code explicitly checks for if there are any Unpublished rules first before attempting to proceed further – no point in running code unnecessarily!
  • Instead of activating each rule one-by-one using an Execute request, all of the requests are collected together as part of an ExecuteMultipleRequest, given that we now know the performance benefits that this can have.
  • Tracing has been implemented in liberal amounts, to provide remote debugging from within CRM/D365E.

Here’s the code – just copy into an empty class file on your plugin project, modify the namespace to reflect the name of your project and you will be good to go!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Crm.Sdk.Messages;

namespace MyPlugin.Plugins
{
    public class PostPublishAll_PublishDuplicateDetectionRules : IPlugin 
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            //Obtain the execution context from the service provider.

            IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

            //Get a reference to the Organization service.

            IOrganizationServiceFactory factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
            IOrganizationService service = factory.CreateOrganizationService(context.UserId);

            //Extract the tracing service for use in debugging sandboxed plug-ins

            ITracingService tracing = (ITracingService)serviceProvider.GetService(typeof(ITracingService));

            tracing.Trace("Tracing implemented successfully!");

            if (context.MessageName == "PublishAll")

            {
                PublishRules(service, tracing);
            }
        }

        private void PublishRules(IOrganizationService service, ITracingService tracing)

        {
            EntityCollection rules = GetDuplicateDetectionRules(service);

            tracing.Trace("Obtained " + rules.TotalRecordCount.ToString() + " duplicate detection rules.");

            if (rules.TotalRecordCount >= 1)

            {
                // Create an ExecuteMultipleRequest object.
                ExecuteMultipleRequest request = new ExecuteMultipleRequest()
                {
                    // Assign settings that define execution behavior: don't continue on error, don't return responses. 
                    Settings = new ExecuteMultipleSettings()
                    {
                        ContinueOnError = false,
                        ReturnResponses = false
                    },
                    // Create an empty organization request collection.
                    Requests = new OrganizationRequestCollection()
                };

                //Create a collection of PublishDuplicateRuleRequests, and execute them in one batch

                foreach(Entity entity in rules.Entities)

                {

                    PublishDuplicateRuleRequest publishReq = new PublishDuplicateRuleRequest { DuplicateRuleId = entity.Id };
                    request.Requests.Add(publishReq);
                    
                }

                service.Execute(request);

            }

            else

            {
                tracing.Trace("Plugin execution cancelled, as there are no duplicate detection rules to publish.");
                return;
            }
        }

        private EntityCollection GetDuplicateDetectionRules(IOrganizationService service)

        {
            QueryExpression qe = new QueryExpression("duplicaterule");

            qe.ColumnSet = new ColumnSet("duplicateruleid");

            ConditionExpression condition1 = new ConditionExpression();
            condition1.AttributeName = "statecode";
            condition1.Operator = ConditionOperator.Equal;
            condition1.Values.Add(0);

            ConditionExpression condition2 = new ConditionExpression();
            condition2.AttributeName = "statuscode";
            condition2.Operator = ConditionOperator.Equal;
            condition2.Values.Add(0);

            FilterExpression filter = new FilterExpression();
            filter.FilterOperator = LogicalOperator.And;
            filter.Conditions.Add(condition1);
            filter.Conditions.Add(condition2);

            qe.Criteria.AddFilter(filter);

            //Have to add this, otherwise the record count won't be returned correctly

            qe.PageInfo.ReturnTotalRecordCount = true;

            return service.RetrieveMultiple(qe);

        } 
    }
}

The only caveat with the above is that it is arguably only useful for if you are regularly importing Unmanaged, as opposed to Managed solutions, as the Publish All Customizations option is not displayed on the import wizard for unmanaged solutions. Nevertheless, by rolling out the above into your environment, you no longer need to scrabble around for the mental note you have to make when performing a solution update 🙂

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Post navigation