Set Multi-OptionSet values with a custom workflow activity
Views (1338)
As the relatively new multi-optionset field type arrived with v.9 of Dynamics 365 the need to set values via workflow is quite a common requirement.
So I searched within the community to find some ideas on how to solve and create such a workflow activity and so I stumbled upon Demian Raschkovan’s Workflow Tools with can be found on his github repository:
It gave me some basic ideas to reach my requirements which are:
- Should be generic for any type of entity
- Ability to specify the attribute name of the required multi-optionset for that entity
- Provide a list of multi-optionset values (comma-separated)
- Keep existing values (True/Yes => add provided values / False/No => replace all values with the provided values
- Remove specific value(s) from an existing set of values
My code sample (without other dependencies):
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Workflow;
using System;
using System.Activities;
using Microsoft.Xrm.Sdk.Query;
using My.CRM.Code.Shared;
namespace My.CRM.Workflows
{
public class SetMultiOptionSetValues : WorkFlowActivityBase
{
[Input("Record URL")]
[RequiredArgument]
public InArgument<string> RecordUrl { get; set; }
[Input("Attribute Name")]
[RequiredArgument]
public InArgument<string> AttributeName { get; set; }
[Input("Multi-OptionSet Values (,-separated)")]
[RequiredArgument]
public InArgument<string> MultiOptionSetValues { get; set; }
[Input("Keep Existing Values")]
[Default("false")]
public InArgument<bool> KeepExistingValues { get; set; }
[Input("Remove Specific Values")]
[Default("false")]
public InArgument<bool> RemoveSpecificValues { get; set; }
protected override void ExecuteActivity()
{
string attributeName = this.AttributeName.Get<string>(this.ExecutionContext);
OptionSetValueCollection optionSetValues = GetMultiOptionSetValues();
EntityReference entityReference = GetEntityReference();
UpdateEntity(entityReference, attributeName, optionSetValues);
}
private OptionSetValueCollection GetMultiOptionSetValues()
{
var optionSetValues = this.MultiOptionSetValues.Get<string>(this.ExecutionContext);
string[] optionSetValuesArray = optionSetValues.Split(',');
if (optionSetValuesArray == null || optionSetValuesArray.Length == 0)
{
this.TraceMessage("No optionset values could be found.");
return null;
}
else
{
OptionSetValueCollection osvCol = new OptionSetValueCollection();
foreach (string optionSetValueStr in optionSetValuesArray)
{
int value = 0;
if (Int32.TryParse(optionSetValueStr, out value))
{
OptionSetValue optionSetValue = new OptionSetValue(value);
osvCol.Add(optionSetValue);
}
else
throw new InvalidWorkflowException("The provided optionset value is not a valid integer '" + optionSetValueStr + "'.");
}
return osvCol;
};
}
private EntityReference GetEntityReference()
{
var recordUrl = this.RecordUrl.Get<string>(this.ExecutionContext);
if (string.IsNullOrWhiteSpace(recordUrl))
throw new ArgumentNullException("Record URL is empty.");
return new DynamicUrlParser(recordUrl).GetEntityReference(this.OrganizationService);
}
private void UpdateEntity(EntityReference entityReference, string attributeName, OptionSetValueCollection newOptionSetValues)
{
if (entityReference == null || newOptionSetValues == null || string.IsNullOrWhiteSpace(attributeName))
return;
OptionSetValueCollection existingOptionSetValues = GetExistingOptionSetValues(entityReference, attributeName);
Entity targetEntity = new Entity(entityReference.LogicalName, entityReference.Id);
targetEntity.Attributes.Add(attributeName, MergeOptionSetCollections(newOptionSetValues, existingOptionSetValues));
this.TraceMessage("Updating the entity record...");
this.OrganizationService.Update(targetEntity);
this.TraceMessage("Entity record updated successfully.");
}
private OptionSetValueCollection GetExistingOptionSetValues(EntityReference entityReference, string attributeName)
{
this.TraceMessage("Retrieving existing values...");
bool removeValues = this.RemoveSpecificValues.Get<bool>(this.ExecutionContext);
bool attributeValues = this.KeepExistingValues.Get<bool>(this.ExecutionContext);
if (!attributeValues && !removeValues)
return null;
Entity record = this.OrganizationService.Retrieve(entityReference.LogicalName, entityReference.Id, new ColumnSet(new string[] { attributeName }));
this.TraceMessage("Existing values have been retrieved correctly");
if (record != null && record.Contains(attributeName))
return record[attributeName] as OptionSetValueCollection;
else
return null;
}
private OptionSetValueCollection MergeOptionSetCollections(OptionSetValueCollection newValues, OptionSetValueCollection existingValues)
{
this.TraceMessage("Merging new and exiting multi-select optionset values...");
if (existingValues == null && newValues == null)
return new OptionSetValueCollection();
if (existingValues == null)
return newValues;
if (newValues == null)
return existingValues;
bool removeValues = this.RemoveSpecificValues.Get<bool>(this.ExecutionContext);
foreach (OptionSetValue newValue in newValues)
{
if (!existingValues.Contains(newValue) && !removeValues)
existingValues.Add(newValue);
else if (existingValues.Contains(newValue) && removeValues)
existingValues.Remove(newValue);
}
this.TraceMessage("New and exiting multi-select optionset values have been merged correctly. Total options: {0} ", existingValues.Count);
return existingValues;
}
}
}
This was originally posted here.

Like
Report
*This post is locked for comments