前言
在制作 SSRS 分页报表 时,如果需要在报表中展示 多选项(Multi Select Option Set)字段,通常会遇到一个限制:
多选项字段无法直接作为查询字段添加到数据集中。在使用 FetchXML 配置数据集时,系统会直接给出提示并阻止该操作。

虽然有点蠢,但是也没有办法。在实际项目中,一个通用且可行的解决方案是:
在实体上额外新增一个文本字段,用于存储多选项的“显示名称”, 然后通过 插件(Plugin)或工作流(Workflow),在多选项字段发生变化时,同步更新该文本字段。 报表中只使用这个文本字段进行展示即可。
至于多选项之间的分隔符(通常使用英文逗号 ,),只需与业务用户提前约定好即可。
示例说明
以 Contact 实体 为例,存在如下两个字段,当用户更新 gdh_multi_select 时,自动将所选项的显示名称拼接后,写入 gdh_multi_select_text 字段。
- Multi Select,字段名:
gdh_multi_select - Multi Select (Text),字段名:
gdh_multi_select_text

存储方式
方式一:插件
插件方案适合对性能、稳定性和一致性要求较高的场景。
插件完整代码见文末
将插件注册到 Contact 实体,并添加以下步骤即可:
- Message:Create or Update
- Stage:Post-Operation
- Filtering Attributes:gdh_multi_select
方式二:工作流
如果你更偏向低代码方案,可以借助第三方工作流工具 Dynamics-365-Workflow-Tools 来实现。它的 Github 仓库地址如下:
https://github.com/demianrasko/Dynamics-365-Workflow-Tools
安装 Dynamics-365-Workflow-Tools
- 打开 GitHub 仓库,进入 Releases(页面右下角)
- 下载 Dynamics 365 Workflow Tools Solution(建议使用 Latest 版本)
- 将解决方案导入到系统中,并按向导完成安装
新建工作流
在 Power Apps 中新建 Workflow:

填写一个有意义的名称,并选择空白模板后点击 Create。

选择 Workflow-Tools → 选择 GetMultiSelectOptionSet

点击 Set Properties

填写/选择 参数 (参数如下) → 保存并关闭
| # | 参数 |
|---|---|
| 1 | Source Record URL |
| 2 | Attribute Name |
| 3 | Retrieve Options Names |
添加更新步骤 → 点击 Set Properties

为字段赋值 → 保存并关闭

最后激活工作流即可
插件代码
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;
}
}
}
如果本文对你有所帮助,可以请我喝杯咖啡
(完)