Dynamics 365中定制包括一个流水号但不会产生重复编号的功能

我是微软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并设置回自动编号设置记录。这样就可以了。

 

posted @ 2021-01-31 23:13  微软MVP(15-18)罗勇  阅读(782)  评论(0编辑  收藏  举报