這是我的第525篇原創文章,寫於2025年11月26日。

Business Process Flow,簡稱BPF,它的介紹請參考 Business process flows overview 。通過前端代碼與它進行交互請參考 formContext.data.process (Client API reference) 和 formContext.ui.process (Client API reference) 。通過服務器端代碼與它進行交互請參考:https://github.com/microsoft/Dynamics365-Apps-Samples/blob/master/samples-from-msdn/WorkWithBPF/WorkWithBPF.cs 。

如果需要根據狀態的變化,自動跳轉到不同的階段的話,可以先使用 RetrieveProcessInstancesRequest 消息查詢出現在的BPF實例記錄信息,然後再使用 RetrieveActivePathRequest 消息查詢出這個BPF實例的所有Stage,如果找到要設置的Stage的話,就設置當前BPF實例的activestageid 為這個stage就可以了,當然如果這個是最後一個Stage,估計還需要結束這個BPF,那麼就是將這個BPF實例的statecode設置為1,statuscode設置為2。我直接提供一個函數如下:

public static void SetBusinessProcessFlowStage(IOrganizationService service, EntityReference targetEF, string bpfTableLogicalName, string activeStageName,bool isFinishBPF = false)
{
    RetrieveProcessInstancesRequest procOpp1Req = new RetrieveProcessInstancesRequest
    {
        EntityId = targetEF.Id,
        EntityLogicalName = targetEF.LogicalName
    };
    RetrieveProcessInstancesResponse procInstanceResp = (RetrieveProcessInstancesResponse)service.Execute(procOpp1Req);
    if (procInstanceResp.Processes.Entities.Any())
    {
        RetrieveActivePathRequest pathReq = new RetrieveActivePathRequest
        {
            ProcessInstanceId = procInstanceResp.Processes.Entities[0].Id
        };
        RetrieveActivePathResponse pathResp = (RetrieveActivePathResponse)service.Execute(pathReq);
        var nextActiveStageId = Guid.Empty;
        for (int i = 0; i < pathResp.ProcessStages.Entities.Count; i++)
        {
            if (pathResp.ProcessStages.Entities[i].GetAttributeValue<string>("stagename").Equals(activeStageName, StringComparison.OrdinalIgnoreCase))
            {
                nextActiveStageId = new Guid(pathResp.ProcessStages.Entities[i].Attributes["processstageid"].ToString());
            }
        }
        if (nextActiveStageId != Guid.Empty)
        {
            Entity retrievedProcessInstance = service.Retrieve(bpfTableLogicalName, procInstanceResp.Processes.Entities[0].Id, new ColumnSet("activestageid"));
            retrievedProcessInstance["activestageid"] = new EntityReference("processstage", nextActiveStageId);
            service.Update(retrievedProcessInstance);
            if(isFinishBPF)
            {
                var updateEntity = new Entity(bpfTableLogicalName, procInstanceResp.Processes.Entities[0].Id);
                updateEntity["statecode"] = new OptionSetValue(1);
                updateEntity["statuscode"] = new OptionSetValue(2);
                service.Update(updateEntity);
            }
        }
    }
}


還有碰到的情況就是一個表有多個BPF,如果要切換BPF呢?這就是要設置使用BPF的這個表的 processid 這個字段的值為BPF的Id。那如何找出找個BPF的Id呢?參考如下代碼:

        public Entity GetProcessByNameandType(string processName, workflow.category_OptionSet processType, workflow.statuscode_OptionSet statusCode)
        {
            QueryExpression query = new QueryExpression("workflow");
            query.ColumnSet = new ColumnSet("name");
            query.Criteria.AddCondition("name", ConditionOperator.Equal, processName);
            query.Criteria.AddCondition("category", ConditionOperator.Equal, 4);
            query.Criteria.AddCondition("statuscode", ConditionOperator.Equal, 4);
            EntityCollection results = orgService.RetrieveMultiple(query);
            return results.Entities.FirstOrDefault();
        }

找到的這個Entity的Id就是我們要的值,然後參考下面代碼設置就可以:

var updateEntity = new Entity(targetEntity.LogicalName, targetEntity.Id);
updateEntity["processid"] = bpfEntity.Id;
orgService.Update(updateEntity);


但是還有一個問題,如果你切換了BPF後,也要設置這個新的BPF實例的階段怎麼辦?如果你立即或者哪怕等待半分鐘去設置,是不會有用的。因為切換了BPF後,這個新的BPF實例的建立是異步的,立即或者哪怕等待半分鐘去設置階段的時候根本還找不到這個BPF的實例,那設置自然會失敗。怎麼辦?那就是這個BPF實例所在的表註冊一個Create消息的Post階段的異步插件,在插件代碼中調用我前面提供的函數  SetBusinessProcessFlowStage 就可以了。

還有最後一個問題,可能你記錄創建的時候使用的A 這個BPF,後來切換到B這個BPF了,你希望打開表單的時候自動切換到B這個BPF顯示,怎麼辦?這時候用客户端變成來解決,示例代碼如下:

        static onLoad(executionContext) {
            var formContext = executionContext.getFormContext();
            var statusCode = formContext.getAttribute("statuscode").getValue();

            if (statusCode === 100000005) {
                var activeProcess = formContext.data.process.getActiveProcess();
                var activeProcessName = activeProcess.getName();

                if (!activeProcessName.includes("BPF B Name")) {
                    formContext.data.process.getEnabledProcesses(function (processes) {
                        const processId = Object.keys(processes).find(k => processes[k].includes("BPF B Name"));
                        if (processId) {
                            formContext.data.process.setActiveProcess(processId, function (result) {
                                if (result === "invalid") {
                                    Xrm.Utility.alertDialog("Failed to set process to Abandon.");
                                }
                            });
                        }
                    });
                }
            }
        }