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:
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:
- Multi Select, Field Name:
gdh_multi_select
- Multi Select (Text), Field Name:
gdh_multi_select_text
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
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;
}
}
}
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
- Visit the Dynamics-365-Workflow-Tools GitHub repository.
- Navigate to the Releases section (bottom right) and download the Dynamics 365 Workflow Tools Solution (it’s recommended to download the Latest version).
- Import the downloaded solution into your system, following the wizard steps.
Creating a New Workflow
Create a new Workflow:
Give the workflow a meaningful name and click Create:
Select Workflow-Tools and choose GetMultiSelectOptionSet:
Click Set Properties:
Fill in/select the parameters and save/close:
- Source Record URL
- Attribute Name
- Retrieve Options Names
Add an update step and click Set Properties:
Assign values to the field and save/close:
Finally, activate the workflow to complete the process.
Comments