在标准实体特殊消息上注册插件及Dynamics CRM 2015中计算字段的使用
关注本人微信和易信公众号: 微软动态CRM专家罗勇 ,回复157或者20151005可方便获取本文,同时可以在第一时间得到我发布的最新的博文信息,follow me!
前面的 插件系列博客教程 讲述了新增记录(Create)、删除记录(Delete) 、更新记录(Update)和更改记录状态(SetState)插件的实例,也提到了系统标准实体会有一些特殊的消息,本博文以案例实体为例讲解一些特殊的消息。为了便于讲述,我这里模拟一个场景,就是解决案例的时候记载下解决案例的人和解决案例的时间,如果案例被重新激活的话,清除之前记载的解决案例的人和解决案例的时间。
首先我们了解一下案例(Case)实体的状态字段信息,从元数据可以得知如下,因为我觉得翻译得不大好,提供了中文对应的英文:
状态 | Status | 状态值 | 状态描述 | Status Reason | 状态描述值 |
可用 | Active | 0 | 正在进行 | In Progress | 1 |
暂侯 | On Hold | 2 | |||
等待详情 | Waiting for Details | 3 | |||
正在研究 | Researching | 4 | |||
已解析 | Resolved | 1 | 问题已解决 | Problem Solved | 5 |
提供的信息 | Information Provided | 1000 | |||
已取消 | Canceled | 2 | 已取消 | Canceled | 6 |
合并 | Merged | 2000 |
我个人认为 合并 如果翻译成 已合并,提供的信息 如果翻译成 信息已提供 会好一点,这些英文用的都是过去式,代表已经做完的意思,翻译成中文的时候带上 已 这个完成意思的词会稍微好点。已解析 翻译成 已解决 也会好点,我觉得大家对这个已解析的意思不会很直观明白。
我先创建两个字段来保存实际的数据,第一个字段是解决者,是普通字段:
![](https://img2018.cnblogs.com/blog/779910/201906/779910-20190604230951874-1628486160.jpg)
第二个字段是 解决时间,注意字段类型是 计算 。计算字段是Dynamics CRM 2015新增的字段类型,以前没有字段类型这个概念。
![](https://img2018.cnblogs.com/blog/779910/201906/779910-20190604231043727-671213911.jpg)
设置的算表达式如下,这是因为我们知道,案例解决后就是只读的,那么案例的最后更新时间就可以作为案例的解决时间:
![](https://img2018.cnblogs.com/blog/779910/201906/779910-20190604231135916-389398246.jpg)
我这里设置 关闭时间 计算字段具体逻辑如下:
![](https://img2018.cnblogs.com/blog/779910/201906/779910-20190604231317443-296945357.jpg)
![](https://img2018.cnblogs.com/blog/779910/201906/779910-20190604231357929-2035145506.jpg)
保存并发布以后,我们可以在系统中看到只有状态是 已解析 的才会有解决时间,直接通过计算字段就可以解决了,符合我们的要求,不需要撰写代码就可以做到,是我做Dynamics CRM定制时候的首选。
![](https://img2018.cnblogs.com/blog/779910/201906/779910-20190604231445362-244090880.jpg)
我们知道可以对案例执行 解决案例 、取消案例 操作,执行这两个操作后的案例还可以执行 重新激活案例 操作,这些操作都会涉及到 案例实体 状态的变更,因为我们这个需求是 状态 变成已解析的时候设置解决者,所以需要知道变更后的状态是 已解析 ,我有考虑过通过实时工作流来做,但是问题是若是在状态变更之后触发运行,因为案例的状态是已解析了,就不能更改了。若是在状态变更之前触发运行,又不知道这个案例的状态是要变成 已解析 还是 已取消,所以用实时工作流有问题。当然我尝试过,如果只要是状态变成 已解析 或者 已取消 就设置 解决者为操作用户的话是可以用实时工作流来做的,将实时工作流设置在状态变更之前触发没问题。但是还有一个问题,如果 重新激活案例 的话,目前来看是无法在工作流中将实体字段的值设置为空值(null)的,所以我这里用插件来做。不知道以后的版本是否可以设置为空值。
我们从SDK中可以知道案例实体支持一些特殊的消息,解决案例操作执行的便是 CloseIncidentRequest 消息,SDK中说明截图如下:
![](https://img2018.cnblogs.com/blog/779910/201906/779910-20190604231600587-448281019.jpg)
那么如果注册插件,这个应该注册到哪个消息上?我们看看 SDK\Message-entity support for plug-ins.xlsx 这个excel中案例实体支持的消息如下,应该是 Close 这个消息:
![](https://img2018.cnblogs.com/blog/779910/201906/779910-20190604231650081-1251826086.jpg)
那好我们就在这个消息上注册个插件看看,注意要注册到 Pre-Operation 这个阶段,因为如果注册到Post-Operation 阶段,代码执行的时候案例已经只读了,如果还对案例执行更新操作会报错的。
![](https://img2018.cnblogs.com/blog/779910/201906/779910-20190604231733112-405005206.jpg)
我这里使用的代码如下:
protected void ExecutePreCaseClose(LocalPluginContext localContext) { if (localContext == null) { throw new ArgumentNullException("localContext"); } // TODO: Implement your custom Plug-in business logic. IPluginExecutionContext context = localContext.PluginExecutionContext; IOrganizationService service = localContext.OrganizationService; Entity incidentResolutionEntity = context.InputParameters.Contains("IncidentResolution") ? (Entity)context.InputParameters["IncidentResolution"] : null; if (incidentResolutionEntity != null) { Entity incidentEntity = service.Retrieve("incident", incidentResolutionEntity.GetAttributeValue<EntityReference>("incidentid").Id, new ColumnSet("new_resolvedby")); incidentEntity["new_resolvedby"] = new EntityReference("systemuser", context.InitiatingUserId); service.Update(incidentEntity); } }
然后我们来测试下,将 案例 标题为 产品相关问题 (示例) 的案例关闭,打开这个案例,点击 解决案例 将其解决。
![](https://img2018.cnblogs.com/blog/779910/201906/779910-20190604231829005-2047177368.jpg)
随后我快速搜索出这个案例,看到记载了案例的 解决者和解决时间,符合要求。
![](https://img2018.cnblogs.com/blog/779910/201906/779910-20190604231924080-164814484.jpg)
![](https://img2018.cnblogs.com/blog/779910/201906/779910-20190604232015691-2118956709.jpg)
我这里使用的代码如下,大部分代码和前面博文类似,只是增加了一点代码:
protected void ExecutePostCaseSetStateDynamicEntity(LocalPluginContext localContext) { try { if (localContext == null) { throw new ArgumentNullException("localContext"); } IPluginExecutionContext context = localContext.PluginExecutionContext; IOrganizationService service = localContext.OrganizationService; Entity preImageEntity = (context.PreEntityImages != null && context.PreEntityImages.Contains(this.preImageAlias)) ? context.PreEntityImages[this.preImageAlias] : null; // TODO: Implement your custom Plug-in business logic. OptionSetValue preStateCode = preImageEntity.Contains("statecode") ? preImageEntity.GetAttributeValue<OptionSetValue>("statecode") : null; OptionSetValue preStatusCode = preImageEntity.Contains("statuscode") ? preImageEntity.GetAttributeValue<OptionSetValue>("statuscode") : null; if (context.InputParameters.Contains("EntityMoniker") && context.InputParameters["EntityMoniker"] is EntityReference) { EntityReference currentEntityRef = (EntityReference)context.InputParameters["EntityMoniker"]; OptionSetValue postStateCode = context.InputParameters.Contains("State") ? (OptionSetValue)context.InputParameters["State"] : null; OptionSetValue postStatusCode = context.InputParameters.Contains("Status") ? (OptionSetValue)context.InputParameters["Status"] : null; var noteText = new StringBuilder("SetStateDynamicEntity消息执行前的statecode字段值为:"); noteText.Append(preStateCode == null ? "null" : preStateCode.Value.ToString()); noteText.Append(";statuscode字段值为:"); noteText.Append(preStatusCode == null ? "null" : preStatusCode.Value.ToString()); noteText.Append(";SetStateDynamicEntity消息执行时要将的statecode字段值变更为:"); noteText.Append(postStateCode == null ? "null" : postStateCode.Value.ToString()); noteText.Append(";statuscode字段值变更为:"); noteText.Append(postStatusCode == null ? "null" : postStatusCode.Value.ToString()); Entity noteEntity = new Entity("annotation"); noteEntity["isdocument"] = false; noteEntity["notetext"] = noteText.ToString(); noteEntity["objectid"] = currentEntityRef; noteEntity["subject"] = "微软MVP罗勇测试SetStateDynamicEntity消息"; service.Create(noteEntity); //下面才是本篇博文新增的代码,其余的代码和前面博文的代码十分类似 //如果变更状态前是已解析,变更状态后不是已解析,则需要设置解决者字段为空值Null if (preStateCode != null && preStateCode.Value == 1 && postStateCode != null && postStateCode.Value != 1) { Entity incidentEntity = service.Retrieve("incident", currentEntityRef.Id, new ColumnSet("new_resolvedby")); incidentEntity["new_resolvedby"] = null; service.Update(incidentEntity); } } else { throw new Exception(@"输入参数中不包括EntityMoniker参数,或者包括了EntityMoniker参数但是其类型不是实体引用"); } } catch (Exception ex) { throw new InvalidPluginExecutionException(@"执行消息SetStateDynamicEntity的Post阶段插件出错:" + ex.Message + ex.StackTrace); } }
我们来测试一下,使用案例 产品相关问题 (示例) 来测试,我这里先做一个 重新激活案例 的操作。
![](https://img2018.cnblogs.com/blog/779910/201906/779910-20190604232116039-2066486514.jpg)
可以看到增加的注释内容正确:
![](https://img2018.cnblogs.com/blog/779910/201906/779910-20190604232204456-1320856727.jpg)
![](https://img2018.cnblogs.com/blog/779910/201906/779910-20190604232243409-246862082.jpg)
那么我对这个案例执行下 取消案例 操作呢?可以看到产生的注释正确,而且解决者 和 解决时间字段的内容仍然是空值,正确。
![](https://img2018.cnblogs.com/blog/779910/201906/779910-20190604232330722-260243531.jpg)
继续测试重新激活案例,解决案例都表明测试结果满足要求。
稍微总结下,从实例中可以知道在界面上对案例执行 解决案例 的操作虽然改变了案例实体的状态,但是并不会触发注册在SetStateDanamicEntity消息上的插件运行,而其他两个操作 取消案例 和 重新激活案例 则会触发注册在SetStateDanamicEntity消息上的插件运行。如果是实时工作流注册在状态变更之前的话,解决案例 这个操作也会触发实时工作流的运行。