Custom workflow activity to process multiple related records
We often need to process multiple records in a workflow, particular those related to the current target record. Think notifying all bookings when an event is rescheduled, or cancelling all orders when a product is cancelled. Unfortunately the OOTB CRM workflow designer does not have for-each or looping steps that would allow us to achieve this.
I have developed a custom workflow activity that would solve this problem in a generic manner.
How it works
The custom workflow activity takes the following inputs:
- A FetchXML query
- Format parameters to parameterise the FetchXML query
- A workflow to execute for each record returned by the FetchXML query
Essentially the idea is that this step allows us to specify a FetchXML query, then parameterised the query with values from the current record, then triggers a separate workflow for each record returned by the parameterised query.
An example
For example, if a workflow was running on an Account record, and from this we wanted to trigger a separate workflow on all Contacts where the Parent Customer is the current Account record (by name, not ID – more on this later), then we’d define a FetchXML query as below. Notice the {0} placeholder for the condition clause.
1
2
3
4
5
6
7
8
9
10
11
12
13
| < fetch version = "1.0" output-format = "xml-platform" mapping = "logical" distinct = "false" > < entity name = "contact" > < attribute name = "fullname" /> < attribute name = "telephone1" /> < attribute name = "contactid" /> < order attribute = "fullname" descending = "false" /> < link-entity name = "account" from = "accountid" to = "parentcustomerid" alias = "ae" > < filter type = "and" > < condition attribute = "name" operator = "eq" value = "{0}" /> </ filter > </ link-entity > </ entity > </ fetch > |
In the workflow designer, we’d flatten the above query into a single line of text and use this as the FetchXML Query. For the FetchXML Query Format Argument 1, we specify the name of the current Account record. Finally we specify the separate workflow to execute on the Contacts matching our parameterised query. If we needed additional filtering, then we could use the remaining format arguments (use placeholder {1} in the query for FetchXML Query Format Argument 2, {2} for FetchXML Query Format Argument 3, etc.).
How about filtering by current record’s ID?
This is possible, but requires a bit more effort on your behalf. This is because the OOTB workflow designer does not have a way for us to get the ID of the current record. You’d need to develop another custom activity to output the ID of the current record, and then use that output as one of the format arguments for the query. A number of people have already developed such custom activities, here is an example.
For the query, of course you can code this by hand, or you can get Advanced Find to generate it for you. The Advanced Find below will perform the filtering by an Account ID. Download this query and replace the value in the condition clause with one of the placeholders.
Show me the code!
Here is the code for the custom activity. As you can see, it is quite straightforward!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
| namespace BNH.CRM.Activities { using System; using System.Activities; using Microsoft.Crm.Sdk.Messages; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Query; using Microsoft.Xrm.Sdk.Workflow; public sealed class StartWorkflowForRecordsActivity : CodeActivity { [RequiredArgument] [Input( "FetchXML Query" )] public InArgument< string > FetchXmlQuery { get ; set ; } [Input( "FetchXML Query Format Argument 1" )] public InArgument< string > FetchXmlQueryFormatArg1 { get ; set ; } [Input( "FetchXML Query Format Argument 2" )] public InArgument< string > FetchXmlQueryFormatArg2 { get ; set ; } [Input( "FetchXML Query Format Argument 3" )] public InArgument< string > FetchXmlQueryFormatArg3 { get ; set ; } [Input( "FetchXML Query Format Argument 4" )] public InArgument< string > FetchXmlQueryFormatArg4 { get ; set ; } [Input( "FetchXML Query Format Argument 5" )] public InArgument< string > FetchXmlQueryFormatArg5 { get ; set ; } [RequiredArgument] [Input( "Workflow to Execute" )] [ReferenceTarget( "workflow" )] public InArgument<EntityReference> WorkflowToExecute { get ; set ; } protected override void Execute(CodeActivityContext context) { var serviceFactory = context.GetExtension<IOrganizationServiceFactory>(); var service = serviceFactory.CreateOrganizationService(Guid.Empty); //Use current user's ID var recordsToProcess = service.RetrieveMultiple( new FetchExpression(GetFormattedFetchQuery(context))); foreach ( var record in recordsToProcess.Entities) { var workflowRequest = new ExecuteWorkflowRequest { EntityId = record.Id, WorkflowId = this .WorkflowToExecute.Get(context).Id }; service.Execute(workflowRequest); } } private string GetFormattedFetchQuery(CodeActivityContext context) { var query = this .FetchXmlQuery.Get(context); return String.Format(query, this .FetchXmlQueryFormatArg1.Get(context), this .FetchXmlQueryFormatArg2.Get(context), this .FetchXmlQueryFormatArg3.Get(context), this .FetchXmlQueryFormatArg4.Get(context), this .FetchXmlQueryFormatArg5.Get(context)); } } } |
Download the solution
You can download this custom activity as an unmanaged solution here.