Ever So Mysterious, O Business Process Flow | Bitesize Series
Microsoft introduced Business Process Flows (BPF) back in version 2013. Developers haven’t really faced critical issues when controlling the BPF through the UI, except for the rare bug here and there. However, it has been ever so mysterious to actually understand, clearly, how to control it properly through the SDK. Each member of the community would do their own experiments with the BPF feature, to get it to work in every situation possible.
With Dynamics 365 version of CRM, Microsoft changed/tweaked how BPFs work. The change made it a little bit harder from an SDK point of view. They introduced an instance entity for each BPF, with its stages defined as records in a common entity. Moving through the stages is still fairly straightforward; however, skipping stages might cause an issue, and there are a couple of subtle intricacies of how to get it to work flawlessly.
After some experimentation, and a few issues popping up in projects, I was finally able to write that helper method that would work in every situation. In this article, I will go through the different parts of the code. Please note that I will omit error handling and code structure.
Parameters
Assume you will pass the following information as arguments:
- Record: the Reference to the main record utilising the BPF
- E.g. Case Entity
- Process Logical Name: Process Instance Entity Logical Name
- We can set it as a constant in the code
- Process ID: the BPF definition ID
- Can be found in the BPF editor’s URL
- Or, can be read from the Process ID in the Record (sometimes it’s empty though; so not consistent!)
- Stage ID: ID of the target Stage
- Can be set as a constant in the code
- Or, Can be retrieved from the Active Path if all that is required is to move forwards/backwards
- Stage Record: the Reference to the record of the current Stage (for multi-entity BPF)
- Stage Record Lookup Name: the name of the Lookup in the target Stage Entity (multi-entity BPF) pointing to the main record (Record Entity parameter above)
Example
If we have the following information:
- An Entity called Request (new_Request)
- Whose BPF is called Resolution Flow (new_BpfResolutionFlow), having the ID: “bea92f8e-dcf4-40b4-b2cc-7b4f835dcf09”
- A relation to an Entity called Approval (new_Approval), having a Lookup to the Request (new_RequestId)
- The following stages:
- Research (Stage ID: f7bed494-4610-46d3-a07e-07d78e2b5fe9)
- Decision (Stage ID: bca96646-2414-4dba-a970-b522176362ef)
- Approval (points to the Approval Entity, Stage ID: f8db3b4c-585e-474b-8629-529a94dac8f8)
- Closure (Stage ID: 4a3e6cc8-741b-4c3e-8b36-9b73b9bd2268)
If we created a new record of the Request Entity having the ID: “7e591173-6983-404a-a132-aa7e9a46df00”, and a related Approval record having the ID: “74bc3837-a312-4298-ba8a-29a8e284180c”, the arguments passed to the code below would be:
- Record: EntityReference(“new_Request”, “7e591173-6983-404a-a132-aa7e9a46df00”)
- Process Logical Name: “new_BpfResolutionFlow”
- Process ID: “bea92f8e-dcf4-40b4-b2cc-7b4f835dcf09”
- Target Stage ID: “f8db3b4c-585e-474b-8629-529a94dac8f8”
- Stage Record: EntityReference(“new_Approval”, “74bc3837-a312-4298-ba8a-29a8e284180c”)
- Stage Record Lookup Name: “new_RequestId”
BPF Instance
We will start by retrieving the BPF Instance record, which we will use in identifying the Instance ID.
var bpfInstance = ((RetrieveProcessInstancesResponse)service.Execute( new RetrieveProcessInstancesRequest { EntityId = record.Id, EntityLogicalName = record.LogicalName })).Processes?.Entities? .Select( e => new BpfRecord { LogicalName = processLogicalName, Id = e.Id, ProcessId = processId, StageId = e.GetAttributeValue<Guid?>("processstageid") }) .FirstOrDefault(e => e.ProcessId == processId);
In the code above, BpfRecord
is a custom class.
Traversed Path
Next, we need the Active Path of the BPF, which we will use in building the Traversed Path.
var activePath = ((RetrieveActivePathResponse)service.Execute( new RetrieveActivePathRequest { ProcessInstanceId = bpfInstance.Id })).ProcessStages.Entities .Select(s => s.Id);
If the difference between the old and new Traversed Paths is more than one stage, you have to move through the Stages one by one until you reach the target Stage. In other words, CRM does not allow skipping stages anymore (v9). Therefore, we will build the old and new Traversed Paths.
var oldTraversedPath = activePath.TakeWhile(s => s != bpfInstance.StageId) .Union(new[] { bpfInstance.StageId }) .Select(s => s.ToString()).Aggregate((e1, e2) => $"{e1},{e2}"); var newTraversedPath = activePath.TakeWhile(s => s != stageId) .Union(new[] { stageId }) .Select(s => s.ToString()).Aggregate((e1, e2) => $"{e1},{e2}");
Recursion
Compare the paths. If the logic detects a skip, check the direction of movement and recurse over the logic.
var oldPathLength = oldTraversedPath.Split(',').Length; var newPathLength = newTraversedPath.Split(',').Length; if (oldPathLength - newPathLength < -1) { // call this whole logic again (recurse) with the next stage as target } else if (oldPathLength - newPathLength > 1) { // call this whole logic again (recurse) with the previous stage as target }
Move the Stage
Finally, we can update the values that will actually move the stage in CRM.
var updatedProcessRecord = new Entity(bpfInstance.LogicalName) { Id = bpfInstance.Id, [$"bpf_{record.LogicalName}id"] = record, ["activestageid"] = new EntityReference("processstage", stageId) }; if (stageRecord != null) { updatedProcessRecord[$"bpf_{stageRecord.LogicalName}id"] = stageRecord; // before jumping into a stage entity record, it has to be related to the current record first, if not already so if (!string.IsNullOrWhitespace(stageRecordLookupName)) { service.Update( new Entity(stageRecord.LogicalName, stageRecord.Id) { [stageRecordLookupName] = record }); } } service.Update(updatedProcessRecord);
Library
The above code exists in my ‘common’ library, which you can download here.
Current State
It used to be so much easier, but Microsoft has been adding validations upon validations that are making it evermore harder to control the BPF through the SDK. Thankfully, with experimentation, we can figure it out, eventually.
1 Response
[…] get it to work in every situation possible. With Dynamics 365 version of CRM, Microsoft changed… Read the full text. Read Complete Post and […]