D365 – Storing Multi-Select Display Names in Text Fields

Introduction

When creating SSRS paginated reports, you may encounter limitations when trying to add “multi-select” fields. Specifically, FetchXML queries do not allow the addition of multi-select fields, resulting in the following error message:

SSRS Paginated Report Dataset FetchXML Query Does Not Allow Adding Multi-Select Field

While this restriction can be frustrating, a common workaround is to add a new text field to the corresponding entity. Then, using a plugin or workflow, you can populate this text field with the display names of the selected multi-select options. Finally, the report can use this new text field for display purposes, with a predefined delimiter (usually a comma) between the options.

For instance, in the Contact entity, I have the following two fields that require updating through a plugin or workflow when the Multi Select field is modified:

  1. Multi Select, Field Name: gdh_multi_select
  2. Multi Select (Text), Field Name: gdh_multi_select_text

New Multi-Select Field and Corresponding Text Field in Contact Entity

Methods for Storing Multi-Select Display Names in Text Fields

Method 1: Using a Plugin

The following plugin code can be registered to the Contact entity, with Create and Update steps added accordingly.

ContactPostUpdatePlugin.cs

CSHARP
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Metadata;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Collections.Generic;
using System.IdentityModel.Metadata;
using System.Linq;
using System.Runtime.Remoting.Services;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Blog.D365.Plugins.Contact
{
    public class ContactPostUpdatePlugin : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            ITracingService tracer = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
            IPluginExecutionContext context =
                (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
            try
            {
                if (context.InputParameters.Contains("Target") && 
                context.InputParameters["Target"] is Entity)
                {
                    IOrganizationServiceFactory factory =
                        (IOrganizationServiceFactory)serviceProvider.
                        GetService(typeof(IOrganizationServiceFactory));
                    IOrganizationService service = factory.CreateOrganizationService(context.UserId);
                    IOrganizationService serviceAdmin = factory.CreateOrganizationService(null);
                    Entity targetEntityRecord = (Entity)context.InputParameters["Target"];

                    // "context.Stage == 40" -> PostOperation
                    if (context.Stage == 40 && targetEntityRecord.Attributes.Contains("gdh_multi_select") &&
                        (context.MessageName == "Create" || context.MessageName == "Update"))
                    {
                        SetMultiSelectText(serviceAdmin, targetEntityRecord);
                    }
                }
            }
            catch (Exception ex)
            {
                tracer.Trace($"ContactPostUpdatePlugin unexpected exception:\n{ex.Message}");
                throw;
            }
        }

        private void SetMultiSelectText(IOrganizationService organization, Entity contactEn)
        {
            if (contactEn.Attributes.Contains("gdh_multi_select"))
            {
                OptionSetValueCollection optionSetValues =
                    contactEn.GetAttributeValue<OptionSetValueCollection>("gdh_multi_select");
                string roleText =
                    GetMultiSelectOptionSetLabels(
                      organization, 
                      "contact", 
                      "gdh_multi_select", 
                      optionSetValues);
                if (!string.IsNullOrEmpty(roleText))
                {
                    Entity updateContact = new Entity(contactEn.LogicalName, contactEn.Id);
                    updateContact["gdh_multi_select_text"] = roleText;
                    organization.Update(updateContact);
                }
            }
        }

        public static string GetMultiSelectOptionSetLabels(
            IOrganizationService service,
            string entityLogicalName,
            string attributeLogicalName,
            OptionSetValueCollection values,
            int? languageCode = null)
        {
            if (values == null || !values.Any())
                return string.Empty;

            // Retrieve the attribute metadata
            RetrieveAttributeRequest retrieveAttributeRequest = new RetrieveAttributeRequest
            {
                EntityLogicalName = entityLogicalName,
                LogicalName = attributeLogicalName,
                RetrieveAsIfPublished = true
            };

            RetrieveAttributeResponse retrieveAttributeResponse = 
            (RetrieveAttributeResponse)service.Execute(retrieveAttributeRequest);
            MultiSelectPicklistAttributeMetadata attributeMetadata = 
            retrieveAttributeResponse.AttributeMetadata as MultiSelectPicklistAttributeMetadata;

            if (attributeMetadata == null)
                throw new InvalidPluginExecutionException("Attribute is not a Metadata.");

            // Prepare a map from option value to label
            Dictionary<int, string> optionLabels = attributeMetadata.OptionSet.Options.ToDictionary(
                o => o.Value.GetValueOrDefault(),
                o => GetLocalizedLabel(o, languageCode)
            );

            // Map selected values to labels
            var selectedLabels = values
                .Select(v => optionLabels.ContainsKey(v.Value) ? 
                optionLabels[v.Value] : $"(Unknown {v.Value})")
                .ToList();

            return string.Join(", ", selectedLabels);
        }

        private static string GetLocalizedLabel(OptionMetadata option, int? languageCode = null)
        {
            if (languageCode.HasValue)
            {
                var label = option.Label.LocalizedLabels
                    .FirstOrDefault(l => l.LanguageCode == languageCode.Value);
                return label?.Label ?? $"(No label for {option.Value})";
            }
            else
            {
                return option.Label.UserLocalizedLabel?.Label ?? $"(No label for {option.Value})";
            }
        }

        public static int GetCurrentUserLanguageCode(IOrganizationService service)
        {
            WhoAmIResponse whoAmI = (WhoAmIResponse)service.Execute(new WhoAmIRequest());
            Guid userId = whoAmI.UserId;
            QueryExpression query = new QueryExpression("usersettings");
            query.Criteria.AddCondition(
              new ConditionExpression("systemuserid", ConditionOperator.Equal, userId));
            query.ColumnSet.AddColumns("uilanguageid");
            Entity result = service.RetrieveMultiple(query).Entities.FirstOrDefault();
            return result != null && result.Attributes.Contains("uilanguageid")
                ? (int)result["uilanguageid"]
                : 1033;
        }
    }
}
Click to expand and view more

Method 2: Using a Workflow

For this workflow method, I utilized a third-party workflow tool: Dynamics-365-Workflow-Tools. You can find its GitHub repository here:

https://github.com/demianrasko/Dynamics-365-Workflow-Tools

Installing Dynamics-365-Workflow-Tools

  1. Visit the Dynamics-365-Workflow-Tools GitHub repository.
  2. Navigate to the Releases section (bottom right) and download the Dynamics 365 Workflow Tools Solution (it’s recommended to download the Latest version).
  3. Import the downloaded solution into your system, following the wizard steps.

Creating a New Workflow

Create a new Workflow:

Create New Workflow - 01

Give the workflow a meaningful name and click Create:

Create New Workflow - 02

Select Workflow-Tools and choose GetMultiSelectOptionSet:

Create New Workflow - 03

Click Set Properties:

Create New Workflow - 04

Fill in/select the parameters and save/close:

Create New Workflow - 05

Add an update step and click Set Properties:

Create New Workflow - 06

Assign values to the field and save/close:

Create New Workflow - 07

Finally, activate the workflow to complete the process.

Copyright Notice

Author: Donghai

Link: https://gdhblog.com/posts/d365/store-multiple-choice-names/

License: CC BY-NC-SA 4.0

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. Please attribute the source, use non-commercially, and maintain the same license.

Comments

Start searching

Enter keywords to search articles

↑↓
ESC
⌘K Shortcut