在C#插件中更改业务流程阶段

8
我正在按照this文章的指导,尝试在c#插件中更改我的业务流程阶段。我能够将阶段前进到下一阶段,但是当我尝试返回之前的阶段时,我收到一个错误。以下是我从动态UI中收到的错误。当我调试插件时,我收到了一个不包含任何信息的FaultException<OrganizationServiceFault>异常。为什么会出现这个错误,我该如何修改代码以成功返回到我的业务流程流程中的上一个阶段?

错误

Unhandled Exception: System.ServiceModel.FaultException`1[[Microsoft.Xrm.Sdk.OrganizationServiceFault, Microsoft.Xrm.Sdk, Version=8.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]: An unexpected error occurred.
Detail: <OrganizationServiceFault xmlns="http://schemas.microsoft.com/xrm/2011/Contracts" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <ActivityId>5df51362-b7c1-4817-a8d0-de2d63b15c17</ActivityId>
  <ErrorCode>-2147220970</ErrorCode>
  <ErrorDetails xmlns:a="http://schemas.datacontract.org/2004/07/System.Collections.Generic" />
  <Message>An unexpected error occurred.</Message>
  <Timestamp>2018-07-19T18:55:42.6625925Z</Timestamp>
  <ExceptionSource i:nil="true" />
  <InnerFault>
    <ActivityId>5df51362-b7c1-4817-a8d0-de2d63b15c17</ActivityId>
    <ErrorCode>-2147220970</ErrorCode>
    <ErrorDetails xmlns:a="http://schemas.datacontract.org/2004/07/System.Collections.Generic" />
    <Message>System.NullReferenceException: Microsoft Dynamics CRM has experienced an error. Reference number for administrators or support: #0D309052</Message>
    <Timestamp>2018-07-19T18:55:42.6625925Z</Timestamp>
    <ExceptionSource i:nil="true" />
    <InnerFault i:nil="true" />
    <OriginalException i:nil="true" />
    <TraceText i:nil="true" />
  </InnerFault>
  <OriginalException i:nil="true" />
  <TraceText i:nil="true" />
</OrganizationServiceFault>

插件

if (localContext == null)
{
    throw new ArgumentNullException("localContext");
}

IPluginExecutionContext context = localContext.PluginExecutionContext;
IOrganizationService service = localContext.OrganizationService;

Client client = (Client)service.Retrieve(
    Client.LogicalName,
    new Guid("75FE165F-848B-E811-80F3-005056B33317"),
    new ColumnSet(new String[]{
        Client.Properties.ClientId
    })
);

client.ChangeStage(service);

更改舞台

public void ChangeStage(IOrganizationService service)
{
    // Get Process Instances
    RetrieveProcessInstancesRequest processInstanceRequest = new RetrieveProcessInstancesRequest
    {
        EntityId = this.Id,
        EntityLogicalName = this.LogicalName
    };

    RetrieveProcessInstancesResponse processInstanceResponse = (RetrieveProcessInstancesResponse)service.Execute(processInstanceRequest);

    // Declare variables to store values returned in response
    int processCount = processInstanceResponse.Processes.Entities.Count;
    Entity activeProcessInstance = processInstanceResponse.Processes.Entities[0]; // First record is the active process instance
    Guid activeProcessInstanceID = activeProcessInstance.Id; // Id of the active process instance, which will be used later to retrieve the active path of the process instance

    // Retrieve the active stage ID of in the active process instance
    Guid activeStageID = new Guid(activeProcessInstance.Attributes["processstageid"].ToString());

    // Retrieve the process stages in the active path of the current process instance
    RetrieveActivePathRequest pathReq = new RetrieveActivePathRequest
    {
        ProcessInstanceId = activeProcessInstanceID
    };
    RetrieveActivePathResponse pathResp = (RetrieveActivePathResponse)service.Execute(pathReq);

    string activeStageName = "";
    int activeStagePosition = -1;

    Console.WriteLine("\nRetrieved stages in the active path of the process instance:");
    for (int i = 0; i < pathResp.ProcessStages.Entities.Count; i++)
    {
        // Retrieve the active stage name and active stage position based on the activeStageId for the process instance
        if (pathResp.ProcessStages.Entities[i].Attributes["processstageid"].ToString() == activeStageID.ToString())
        {
            activeStageName = pathResp.ProcessStages.Entities[i].Attributes["stagename"].ToString();
            activeStagePosition = i;
        }
    }

    // Retrieve the stage ID of the next stage that you want to set as active
    activeStageID = (Guid)pathResp.ProcessStages.Entities[activeStagePosition - 1].Attributes["processstageid"];

    // Retrieve the process instance record to update its active stage
    ColumnSet cols1 = new ColumnSet();
    cols1.AddColumn("activestageid");
    Entity retrievedProcessInstance = service.Retrieve("ccseq_bpf_clientsetup", activeProcessInstanceID, cols1);

    // Set the next stage as the active stage
    retrievedProcessInstance["activestageid"] = new EntityReference(ProcessStage.LogicalName, activeStageID);
    service.Update(retrievedProcessInstance);
}

更新

我发现了this篇文章,它解释了如何使用Web API更新舞台。但是当我尝试这种方法时,出现了以下错误:

在有效负载中找不到只有属性注释而没有属性值的未声明属性'activestageid'。在OData中,只有声明过的导航属性和声明过的命名流可以表示为没有值的属性。

我已经尝试了几种'activestageid'的变体,但都没有成功(ActiveStageId、_activestageid_value)。


更新2

根据Arun的反馈,我尝试了以下Web API调用,但没有成功。url中括号内的ID(ccseq_bpf_clientsetups(###))是我从ccseq_bpf_clientsetups表中的BusinessProcessFlowInstanceId获取的。我从ProcessStageBase表中的ProcessStageId获取了processstages ID。

// Attempt 1
PATCH /COHEN/api/data/v8.2/ccseq_bpf_clientsetups(bc892aec-2594-e811-80f4-005056b33317) HTTP/1.1
{ "ActiveStageID@odata.bind": "/processstages(70018854-db7c-4612-915b-2ad7870a8574)"}

// Attempt 2
PATCH /COHEN/api/data/v8.2/ccseq_bpf_clientsetups(bc892aec-2594-e811-80f4-005056b33317) HTTP/1.1
{ "activestageid@odata.bind": "/processstages(70018854-db7c-4612-915b-2ad7870a8574)"}

// Attempt 3
PATCH /COHEN/api/data/v8.2/ccseq_bpf_clientsetups(bc892aec-2594-e811-80f4-005056b33317) HTTP/1.1
{ "ActiveStageId@odata.bind": "/processstages(70018854-db7c-4612-915b-2ad7870a8574)"}

更新3

我下载了jLattimer的CRM Rest Builder,并尝试运行他的工具生成的JavaScript代码。但是,该代码与我之前编写的代码完全相同,不幸的是并没有起作用。此时,我相当有信心,在Web API v8.2中不支持更改阶段。


当前阶段是否有必填字段?这可能会阻止您离开活动阶段。 - jasonscript
1
@jasonscript 不,当前阶段没有必填字段。我们尝试在已完成的BPF和未完成的BPF中回退。在这两种情况下,当前阶段都没有必填字段。在未完成的BPF的情况下,我们正在尝试回退到上一个阶段上有一个必填字段,但是它确实有一个指定的值。 - Tim Hutchison
3个回答

1
我有一些代码,试图将业务流程阶段向前移动,作为自定义工作流步骤(而不是插件)。我在下面发布了它。
我看到的区别是:
- 我正在向前移动(而不是向后) - 我可能没有遵循最佳实践 :) - 我没有检索“活动路径”,我只是获取了该过程的所有可用阶段 - 我还设置了“TraversedPath”属性

代码:

var activeInstancesRequest = new RetrieveProcessInstancesRequest
{
    EntityId          = TargetEntity.Id,
    EntityLogicalName = TargetEntity.LogicalName
};
var activeInstancesResponse = (RetrieveProcessInstancesResponse)base.OrgService.Execute(activeInstancesRequest);
var process = activeInstancesResponse.Processes.Entities.Select(x => x.ToEntity<BusinessProcessFlowInstance>()).ToList();
var stages = base.XrmContext.ProcessStageSet
    .Where(s => s.ProcessId.Id == process.FirstOrDefault().ProcessId.Id)
    .Select(s => new ProcessStage
    {
        ProcessStageId = s.ProcessStageId,
        StageName = s.StageName
    })
    .ToList();

var targetStage = stages.Where(stage => stage.StageName == targetStageName).FirstOrDefault();
if (targetStage != null)
{
    crmWorkflowContext.Trace($"BPF contains target stage (\"{targetStageName}\"). Attempting to update BPF");

    // Setting the Traversed Path is necessary for the Business Process Flow to show the active Stage
    // If this is not updated then although the new Stage is set as current, the previous Stage remains actively selected
    var traversedPath = $"{bpf.TraversedPath},{targetStage.ProcessStageId.Value}";
    var update = new BusinessProcessFlowInstance()
    {
        BusinessProcessFlowInstanceId = bpf.BusinessProcessFlowInstanceId,
        ProcessStageId                = targetStage.ProcessStageId,
        TraversedPath                 = traversedPath   
    };

    xrmContext.Attach(update);
    xrmContext.UpdateObject(update);
}

0

我曾经遇到过同样的问题。其实比你想象的要简单得多。 好的,打开高级查找,选择{您的BPF}。 在查询中添加两列:{您的实体} {遍历路径}。

好的,现在看看实体的遍历路径,找到一个实际上处于之前阶段(您想要返回的那个阶段)的实体。

使用您的代码,您需要动态地分解遍历路径(.Split(','))或类似的方法...删除最后一个阶段(您当前所在的阶段),然后就完成了!

如果当前的遍历路径是一个数组:

string[] currentPath = {"A", "B", "C", "D"};

你之前的路径需要是:

string[] previousPath = {"A", "B", "C"};

以下是在代码中如何执行此操作的示例,假设“entity”是您检索到的实体:

string traversedPath = (string) entity["traversedpath"];
string[] stages = traversedPath.Split(',');
string newPath = "";
//use length - 1 to omit last stage (your current stage)
for (int i = 0; i < stages.Length - 1; i++) 
{ 
     if (i != stages.Length - 1)
     newPath += stages[i] + ",";
     else
     newPath += stages[i];

}
entity["processid"] = new Guid("BPF Guid") //may be optional
entity["stageid"] = new Guid("previous stage guid");
entity["traversedpath"] = newPath;
service.Update(entity);

基本上,遍历路径不会在遍历路径的末尾添加“您之前的阶段”。它想要将遍历路径设置为“您之前的阶段”的原始遍历路径。找出所需阶段的遍历路径,并通过代码中的.Entity["traversedpath"]属性上的.Split(',')方法进行硬编码(如果它只会到达该阶段)或以编程方式执行。

记住,您正在从遍历路径中减去,而不是添加。我经历了很多无效的遍历路径错误才得出这个结论...而且它有效。祝你好运!


0

我进行了快速测试,成功在纯净的v9在线组织中在Lead to Opportunity Sales Process中前进/后退。

我只是通过CRM REST构建器使用Web API PATCH请求进行了简单的测试,并且成功了。

Develop to Propose(前进)

var entity = {};
entity["activestageid@odata.bind"] = "/processstages(3A275C22-FC45-4E89-97FC-41E5EC578743)";

var req = new XMLHttpRequest();
req.open("PATCH", Xrm.Page.context.getClientUrl() + "/api/data/v8.2/leadtoopportunitysalesprocesses(1674DB10-1994-E811-A969-000D3A1A9FA9)", true);

提议开发(向后)

var entity = {};
entity["activestageid@odata.bind"] = "/processstages(BFC9108C-8389-406B-9166-2C3298A2E41F)";

var req = new XMLHttpRequest();
req.open("PATCH", Xrm.Page.context.getClientUrl() + "/api/data/v8.2/leadtoopportunitysalesprocesses(1674DB10-1994-E811-A969-000D3A1A9FA9)", true);

它除了如下所需标记的内容之外没有其他任何东西。

不需要
1574DB10-1994-E811-A969-000D3A1A9FA9 - leadid
919E14D1-6489-4852-ABD0-A63A6ECAAC5D - processid
f99b4d48-7aad-456e-864a-8e7d543f7495,bfc9108c-8389-406b-9166-2c3298a2e41f - traversedpath

需要
1674DB10-1994-E811-A969-000D3A1A9FA9 - businessprocessflowinstanceid
BFC9108C-8389-406B-9166-2C3298A2E41F - activestageid 开发
3A275C22-FC45-4E89-97FC-41E5EC578743 - activestageid 建议


更新:

我已经在v8中成功测试了下面的代码片段[版本1612(8.2.2.2160)(DB 8.2.2.2160)在线]

事实上,它是在没有traversedpath的情况下向后/向前移动的。

var entity = {};
entity["activestageid@odata.bind"] = "/processstages(BFC9108C-8389-406B-9166-2C3298A2E41F)";
entity.traversedpath = "f99b4d48-7aad-456e-864a-8e7d543f7495,bfc9108c-8389-406b-9166-2c3298a2e41f";

var req = new XMLHttpRequest();
req.open("PATCH", Xrm.Page.context.getClientUrl() + "/api/data/v8.0/leadtoopportunitysalesprocesses(E5B70E69-2094-E811-8145-C4346BDCF2F1)", true);

但是以下代码出现了错误请求的错误:

entity.activestageid = {
            Id: "3A275C22-FC45-4E89-97FC-41E5EC578743",
            LogicalName: "processstage"
        };

1
我目前使用的是v8版本,这可能是问题的一部分。此外,根据文档,traversedpath是必需的,如果不包括它,我会有点紧张。 - Tim Hutchison
@TimHutchison 我验证了 traversedpath 在正向和反向计算都很快。我还记得我在 v8 中是手动完成的。 - Arun Vinoth-Precog Tech - MVP
@TimHutchison,别再考虑你的BPF了,即使是OOB BPF更改也无济于事。 :) 你试过了吗?那个无用的"Bad request"还在不断出现... - Arun Vinoth-Precog Tech - MVP
我不太明白你说的话,Arun。你说的“忘掉你的BPF,即使是OOB的BPF更改也不起作用”是什么意思? - Tim Hutchison
@TimHutchison,你有机会在你的环境中使用其他OOB BPF验证代码了吗? - Arun Vinoth-Precog Tech - MVP
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接