我是微软Dynamics 365 & Power Platform方面的工程师/顾问罗勇,也是2015年7月到2018年6月连续三年Dynamics CRM/Business Solutions方面的微软最有价值专家(Microsoft MVP),欢迎关注我的微信公众号 MSFTDynamics365erLuoYong ,回复433或者20210131可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me!
有的项目喜欢编号,当然简单的编号可以考虑使用标准的自动编号功能,可以参考我之前的博文 Dynamics 365 Customer Engagement V9.X新引入的自动编号属性介绍 。
我今天要做个客制化,需求是每月从1开始编号,希望编号不会重复,是否连续没有那么重要,如何做呢?
可以考虑这个官方文档 Scalable Customization Design: Auto-numbering example 提供的思路,大致的做法就是利用锁来做,同时利用一个辅助字段。我这里不多解释了,请参考官方文档。
我具体做法是使用了一个插件,注册在需要生成编号的实体的Create消息的Pre Operation阶段,设置如下:
这个使用的代码如下:
using System; using System.ServiceModel; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Query; namespace CRM.Plugins { public class TestPreCreate : IPlugin { public void Execute(IServiceProvider serviceProvider) { ITracingService tracingService = (ITracingService)serviceProvider.GetService(typeof(ITracingService)); IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext)); if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity) { Entity currentEntity = (Entity)context.InputParameters["Target"]; IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory)); //对编号设置信息的读取更改使用SYSTEM账号进行 IOrganizationService orgAdminSvc = serviceFactory.CreateOrganizationService(null); //IOrganizationService orgSvc = serviceFactory.CreateOrganizationService(context.UserId); var groupName = DateTime.UtcNow.AddHours(8).Year.ToString() + DateTime.UtcNow.AddHours(8).Month.ToString().PadLeft(2, '0'); string fetchXml = string.Format(@"<fetch version='1.0' top='1' mapping='logical' distinct='false'> <entity name='ly_autonumber'> <attribute name='ly_autonumberid' /> <filter type='and'> <condition attribute='ly_name' operator='eq' value='{0}' /> <condition attribute='statecode' operator='eq' value='0' /> </filter> </entity> </fetch>", groupName); var autonumberEC = orgAdminSvc.RetrieveMultiple(new FetchExpression(fetchXml)); if (autonumberEC.Entities.Count == 1) { autonumberEC.Entities[0]["ly_subsidiaryfield"] = DateTime.UtcNow.Ticks.ToString(); orgAdminSvc.Update(autonumberEC.Entities[0]); //再查一次当前编号很重要,否则会出现重复编号 var currentStep = orgAdminSvc.Retrieve(autonumberEC.Entities[0].LogicalName, autonumberEC.Entities[0].Id,new ColumnSet("ly_currentstep")).GetAttributeValue<int>("ly_currentstep"); autonumberEC.Entities[0]["ly_currentstep"] = currentStep + 1; currentEntity["ly_no"] = $"{groupName}-{(currentStep + 1).ToString().PadLeft(6, '0')}"; orgAdminSvc.Update(autonumberEC.Entities[0]); } else { throw new InvalidPluginExecutionException("请联系系统管理员先配置好分组!"); } } } } }
然后如何测试呢?我这里启动5个程序,每个程序用 ExecuteMultipleRequest 消息向Dynamics 365来创建500条记录,使用的代码如下:
class Program { static void Main(string[] args) { try { Console.WriteLine($"要开始,输入Y继续"); var input = Console.ReadLine().ToString().ToUpper(); if (input == "Y") { CrmServiceClient.MaxConnectionTimeout = new TimeSpan(10, 0, 0); CrmServiceClient crmSourceSvc = new CrmServiceClient(ConfigurationManager.AppSettings["ConnStr"]); if (!crmSourceSvc.IsReady) { throw new Exception("连接ConnStr失败!" + crmSourceSvc.LastCrmError); } else { Console.WriteLine($"程序连接到Dynamics 365/Power Apps环境 {crmSourceSvc.ConnectedOrgFriendlyName } 成功!"); } ExecuteMultipleResponse multiRep; ExecuteMultipleRequest multiReqs = new ExecuteMultipleRequest() { Settings = new ExecuteMultipleSettings() { ContinueOnError = true, ReturnResponses = true }, Requests = new OrganizationRequestCollection() }; for (int i = 0; i < 500; i++) { CreateRequest req = new CreateRequest(); var createEntity = new Entity("new_testentity"); createEntity["new_name"] = DateTime.UtcNow.AddHours(8).Ticks.ToString(); req.Target = createEntity; multiReqs.Requests.Add(req); } multiRep = (ExecuteMultipleResponse)crmSourceSvc.Execute(multiReqs); foreach (var Rep in multiRep.Responses) { if (Rep.Fault != null) { Console.WriteLine(Rep.Fault.Message); } } } else { Console.WriteLine("你选择了取消程序运行!"); } Console.WriteLine("程序运行完成,按任意键退出!"); Console.ReadKey(); } catch (Exception ex) { Console.WriteLine("程序运行出错:" + ex.Message + ex.StackTrace); Console.ReadLine(); } } }
然后我进行了测试,可以看到生成了 2500条记录,哪怕是同时开始创建的记录,生成的编号也不一样,而且是顺序的。
从Auto Number设置的Audit History来看,序号是连续生成的。
值得注意的是,这种方法需要生成的代码执行比较快才好,否则容易给人卡顿的感觉,而且在高并发情况下,因为等待过久导致超时也会出现(一个请求执行完毕不能超过2分钟,若超过就会报错)。
这个是因为所以的代码都在同一个事务中,产生新的编号前先update编号设置记录,这时候会锁住记录,然后快速生成下一个编号(此时特别注意的是需要再次查询下当前流水号),复制给当前实体的编号字段,然后创建成功,创建成功后事务结束,对编号设置记录的锁也就释放,其他排队的请求就会接上继续生成。
你可能会问,这个是因为在同一个事务中,如果不在同一个事务中呢?比如是利用异步插件或者自定义工作流活动的代码呢?
那怎么办?没有条件创造条件也要上啊,比如创建事务出来,官方不推荐在插件代码或者自定义工作流活动代码中使用 ExecuteTransactionRequest 来构造。
那我的办法就是找到更新找到的自动编号设置记录后,更新它。我在自动编号设置实体的Update消息上注册一个Pre Opreation的插件(这个代码执行与主操作在同一个事务中,属于同一个事务),监控要更新的字段,我这里一般用一个辅助字段,使用一个Pre Image将这个当前顺序号的值传递给插件代码,在插件代码中处理逻辑并为记录设置新的编号,将顺序号加1并设置回自动编号设置记录。这样就可以了。