纯代码生成基础资料/单据原创
金蝶云社区-田野迷鸟
田野迷鸟
12人赞赏了该文章 2,336次浏览 未经作者许可,禁止转载编辑于2021年08月15日 15:09:33

场景:

1、客户第三方数据保存到中间表,需要从中间表读取数据写入到星空公有云;

2、客户有2个基础资料、12张单据的数据需要同步到星空系统;

3、客户一个月的数据量大概80万条记录(单据比较特殊,只有表头信息),其中有一大半是在月底一次性同步;


开发思路:


开发方案标题优点缺点
方案一后台直接写数据库性能最优如果单据上后续调整计算逻辑,需要修改插件代码
方案二(本示例方案)建立模型调用单据保存方法单据计算逻辑可以自适应,保存逻辑调整也可以自适应性能没直接写数据库快
方案三调用API接口进行保存(未尝试)(未尝试)


性能测试结果(开启5线程):

序号流程名称表记录基数同步记录数耗时(秒)平均(秒)
1
基础资料(新增或更新)
3118610.0196
2单据(只新增)1600万38412936270.0094
3单据(只新增)几十万
3739695320.0014
4

单据(判断原单据不存在则新增)

2021-08-03更新

一百多万415860
48600.0117


详细插件代码示例:


1、引用(不是所有必须的,先使用后删除不需要的就行)

using Kingdee.BOS;
using Kingdee.BOS.App.Data;
using Kingdee.BOS.Core;
using Kingdee.BOS.Core.Bill;
using Kingdee.BOS.Core.DynamicForm;
using Kingdee.BOS.Core.DynamicForm.PlugIn;
using Kingdee.BOS.Core.DynamicForm.PlugIn.Args;
using Kingdee.BOS.Core.Metadata;
using Kingdee.BOS.Core.Metadata.FormElement;
using Kingdee.BOS.Core.Validation;
using Kingdee.BOS.Orm;
using Kingdee.BOS.Orm.DataEntity;
using Kingdee.BOS.ServiceHelper;
using MySql.Data.MySqlClient;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;


2、保存一张单据的入口函数,

        /// <summary>
        /// 根据业务对象类型
        /// </summary>
        /// <param name="ctx">星空上下文</param>
        /// <param name="objectTypeId">单据类型,该参数非必要,根据客户需求增加用作区分不同单据的处理</param>
        /// <param name="mySqlDR">数据集,该参数非必要,本例是对接mySql,结合单据类型在后面填充字段值时使用</param>
        /// <param name="PkValue">单据ID值,该参数非必要,外面读取单据是否已经存在</param>
        /// <param name="strErrorList">收集错误信息,该参数非必要,根据开发设计用作日志记录</param>
        /// <param name="FillBillPropertys">做了个填充字段值的方法指针</param>
        public void ImportBill(Context ctx, string objectTypeId, MySqlDataReader mySqlDR, Object PkValue, ref List<string> strErrorList, Action<IBillView, string, MySqlDataReader, List<string>> FillBillPropertys)
        {

            try
            {

                // 构建一个IBillView实例,通过此实例,可以方便的填写业务对象各属性
                IBillView billView = CreateBillView(ctx, objectTypeId, PkValue,ref strErrorList);

                if (billView == null)
                {
                    strErrorList.Add("构建一个IBillView实例出现异常,请再尝试一次同步。");
                    return;
                }

                // 新建一个空白实例
                ((IBillViewService)billView).LoadData();

                // 触发插件的OnLoad事件:
                // 组织控制基类插件,在OnLoad事件中,对主业务组织改变是否提示选项进行初始化。
                // 如果不触发OnLoad事件,会导致主业务组织赋值不成功
                DynamicFormViewPlugInProxy eventProxy = billView.GetService<DynamicFormViewPlugInProxy>();
                eventProxy.FireOnLoad();

                // 填写业务对象实例各属性
                FillBillPropertys(billView, objectTypeId, mySqlDR, strErrorList);
                //同步赋值有问题,就直接退出
                if (strErrorList.Count > 0)
                {
                    return;
                }

                // 保存业务对象实例
                List<ValidationErrorInfo> errorList = new List<ValidationErrorInfo>();
                SaveBill(ctx, billView, OperateOption.Create(), out errorList);

                foreach (ValidationErrorInfo item in errorList)
                {
                    strErrorList.Add(item.Message);
                }

            }
            catch (Exception ex)
            {
                strErrorList.Add(ex.Message);
            }
        }

3、创建单据视图(该方法基本不用调整)

        /// <summary>
        /// 创建一个单据视图,后续将利用此视图的各种方法,设置业务对象字段值
        /// </summary>
        /// <remarks>
        /// 理论上,也可以直接修改业务对象的数据包达成修改数据的目的
        /// 但是,利用单据视图更具有优势:
        /// 1. 视图会自动触发插件,这样逻辑更加完整;
        /// 2. 视图会自动利用单据元数据,填写字段默认值,不用担心字段值不符合逻辑;
        /// 3. 字段改动,会触发实体服务规则;
        /// 
        /// 而手工修改数据包的方式,所有的字段值均需要自行填写,非常麻烦
        /// </remarks>
        private IBillView CreateBillView(Context ctx, string objectTypeId, object PkValue, ref List<string> strErrorList)
        {
            try
            {

                // 读取业务对象的元数据
                FormMetadata meta = MetaDataServiceHelper.Load(ctx, objectTypeId) as FormMetadata;

                //业务对象整体信息
                Form form = meta.BusinessInfo.GetForm();
                // 动态领域模型服务提供类,通过此类,构建MVC实例
                var provider = form.GetFormServiceProvider();

                // 创建用于引入数据的单据view
                Type type = Type.GetType("Kingdee.BOS.Web.Import.ImportBillView,Kingdee.BOS.Web");
                var billView = (IDynamicFormViewService)Activator.CreateInstance(type);

                // 开始初始化billView:
                // 创建视图加载参数对象,指定各种参数,如FormId, 视图(LayoutId)等
                BillOpenParameter openParam = CreateOpenParameter(ctx, meta, PkValue);
                if (openParam == null)
                {
                    return null;
                }
                billView.Initialize(openParam, provider);
                return billView as IBillView;
            }
            catch (Exception ex)
            {
                strErrorList.Add(ex.Message);
                return null;
            }
        }

4、创建视图加载参数对象(该方法基本不用调整)

        /// <summary>
        /// 创建视图加载参数对象,指定各种初始化视图时,需要指定的属性
        /// </summary>
        /// <param name="meta">元数据</param>
        /// <returns>视图加载参数对象</returns>
        private BillOpenParameter CreateOpenParameter(Context ctx, FormMetadata meta, object PkValue)
        {

            try
            {


                Form form = meta.BusinessInfo.GetForm();
                // 指定FormId, LayoutId
                BillOpenParameter openParam = new BillOpenParameter(form.Id, meta.GetLayoutInfo().Id);
                // 数据库上下文
                openParam.Context = ctx;
                // 本单据模型使用的MVC框架
                openParam.ServiceName = form.FormServiceName;
                // 随机产生一个不重复的PageId,作为视图的标识
                openParam.PageId = Guid.NewGuid().ToString();
                // 元数据
                openParam.FormMetaData = meta;

                // 界面状态:新增 (修改、查看)
                openParam.Status = OperationStatus.ADDNEW;
                openParam.PkValue = null;
                // 单据主键
                if (PkValue != null && Convert.ToInt64(PkValue) > 0)
                {
                    openParam.PkValue = PkValue;
                    openParam.Status = OperationStatus.EDIT;
                }

                // 界面创建目的:普通无特殊目的 (为工作流、为下推、为复制等)
                openParam.CreateFrom = CreateFrom.Default;
                // 基础资料分组维度:基础资料允许添加多个分组字段,每个分组字段会有一个分组维度
                // 具体分组维度Id,请参阅 form.FormGroups 属性
                openParam.GroupId = "";
                // 基础资料分组:如果需要为新建的基础资料指定所在分组,请设置此属性
                openParam.ParentId = 0;
                // 单据类型
                openParam.DefaultBillTypeId = "";
                // 业务流程
                openParam.DefaultBusinessFlowId = "";
                // 主业务组织改变时,不用弹出提示界面
                openParam.SetCustomParameter("ShowConfirmDialogWhenChangeOrg", false);
                // 插件
                List<AbstractDynamicFormPlugIn> plugs = form.CreateFormPlugIns();
                openParam.SetCustomParameter(FormConst.PlugIns, plugs);
                PreOpenFormEventArgs args = new PreOpenFormEventArgs(ctx, openParam);
                //foreach (var plug in plugs)
                //{// 触发插件PreOpenForm事件,供插件确认是否允许打开界面
                //    plug.PreOpenForm(args);
                //}
                if (args.Cancel == true)
                {// 插件不允许打开界面
                 // 本案例不理会插件的诉求,继续....
                }
                // 返回
                return openParam;
            }
            catch (Exception)
            {
                return null;
            }
        }

5、给业务对象各属性赋值,根据不同的业务对象类型(自定义的),分别调用不同方法,方便管理

        /// <summary>
        /// 各属性赋值,填写到IBillView当前所管理的业务对象
        /// </summary>
        /// <param name="billView"></param>
        private static void FillBillPropertys(IBillView billView, string objectTypeId, MySqlDataReader mySqlDR, List<string> strErrorList)
        {

            switch (objectTypeId)
            {
                case "xxx":
                    {
                        FillBillPropertys_xxx(billView, objectTypeId, mySqlDR, strErrorList);
                    }
                    break;
                case "yyy":
                case "zzz":
                case "PSEA_PurAdvance_buffet":
                    {
                        FillBillPropertys_yyyzzz(billView, objectTypeId, mySqlDR, strErrorList);
                    }
                    break;
                default:
                    break;
            }


            #region 赋值示例

            //基础资料:填写ID或者编码
            //dynamicObject = this.View.Model.GetValue("F_ZHMS_OrgId") as DynamicObject;
            //setValue = Convert.ToString(dynamicObject["Number"]);
            //dynamicFormView.SetItemValueByID("FCreateOrgId", 1, 0)
            //dynamicFormView.SetItemValueByNumber("FCreateOrgId", "1", 0)

            //辅助资料:填写编码
            //dynamicObject = this.View.Model.GetValue("F_ZHMS_TaxType") as DynamicObject;
            //setValue = Convert.ToString(dynamicObject["FNumber"]);
            //dynamicFormView.SetItemValueByNumber("FTaxType", setValue, 0);

            //分组:填写编码
            //dynamicObject = this.View.Model.GetValue("F_ZHMS_MaterialGroup") as DynamicObject;
            //setValue = Convert.ToString(dynamicObject["Number"]);
            //dynamicFormView.UpdateValue("FMaterialGroup", 0, setValue);

            //文本:填写内容
            //setValue = Convert.ToString(this.View.Model.GetValue("FDescription", thisRowIndex));
            //dynamicFormView.UpdateValue("FDescription", 0, "描述(JD-001)");

            // 下拉列表:填写枚举值
            //setValue = Convert.ToString(this.View.Model.GetValue("FMaterialSRC", thisRowIndex));
            //dynamicFormView.UpdateValue("FMaterialSRC", 0, "B");        //A、PLM;B、ERP     //取默认值即可

            //数量:填写值
            //setValue = Convert.ToString(this.View.Model.GetValue("F_ZHMS_ZXJJ", thisRowIndex));
            //dynamicFormView.UpdateValue("F_PSEA_ZXJJ", 0, setValue);

            //复选框:true,false
            //dynamicFormView.UpdateValue("FIsKFPeriod", 0, true);


            // 把billView转换为IDynamicFormViewService接口:
            // 调用IDynamicFormViewService.UpdateValue: 会执行字段的值更新事件
            // 调用 dynamicFormView.SetItemValueByNumber :不会执行值更新事件,需要继续调用:
            // ((IDynamicFormView)dynamicFormView).InvokeFieldUpdateService(key, rowIndex);

            #endregion 赋值示例


        }

6、具体的业务对象属性赋值示例

        /// <summary>
        /// xxx的字段填充
        /// </summary>
        /// <param name="billView"></param>
        /// <param name="objectTypeId"></param>
        /// <param name="mySqlDR"></param>
        private static void FillBillPropertys_xxx(IBillView billView, string objectTypeId, MySqlDataReader mySqlDR, List<string> strErrorList)
        {

            string setValue = string.Empty;

            IDynamicFormViewService dynamicFormView = billView as IDynamicFormViewService;

            //公司类别
            setValue = Convert.ToString(mySqlDR["companyType_id"]);
            dynamicFormView.SetItemValueByNumber("FCreateOrgId", setValue, 0);
            dynamicFormView.SetItemValueByNumber("FUseOrgId", setValue, 0);
            CheckBaseFieldValue(billView, "FCreateOrgId", setValue, "公司类别", "创建组织", strErrorList);
            CheckBaseFieldValue(billView, "FUseOrgId", setValue, "公司类别", "使用组织", strErrorList);

            //商户编码
            setValue = Convert.ToString(mySqlDR["merchant_id"]).Trim(' ');
            dynamicFormView.UpdateValue("FNumber", 0, setValue);
            //CheckTextFieldValue(billView, "FNumber", setValue, "商户编码", "商户编码", strErrorList);

            //商户名称
            setValue = Convert.ToString(mySqlDR["merchant_name"]);
            dynamicFormView.UpdateValue("FName", 0, setValue);
            //CheckTextFieldValue(billView, "FName", setValue, "商户名称", "商户名称", strErrorList);

            //商户状态 FState
            setValue = Convert.ToString(mySqlDR["state"]);
            dynamicFormView.UpdateValue("FState", 0, setValue);

            //禁用状态
            dynamicFormView.UpdateValue("FForbidStatus", 0, "A");

            //数据状态
            dynamicFormView.UpdateValue("FDocumentStatus", 0, "C");

        }

7、调用业务对象保存操作

        /// <summary>
        /// 保存,并显示保存结果
        /// </summary>
        /// <param name="billView"></param>
        /// <returns></returns>
        private void SaveBill(Context ctx, IBillView billView, OperateOption saveOption, out List<ValidationErrorInfo> errorList)
        {

            errorList = new List<ValidationErrorInfo>();

            // 设置FormId
            Form form = billView.BillBusinessInfo.GetForm();
            if (form.FormIdDynamicProperty != null)
            {
                form.FormIdDynamicProperty.SetValue(billView.Model.DataObject, form.Id);
            }

            // 调用保存操作 暂存:Draft;保存:Save; 
            IOperationResult saveResult = BusinessDataServiceHelper.Save(
                        ctx,
                        billView.BillBusinessInfo,
                        billView.Model.DataObject,
                        saveOption,
                        "Save");
            // 显示处理结果
            if (saveResult == null)
            {
                //this.View.ShowErrMessage("未知原因导致保存物料失败!");        //失败提示,另外补充
                return;
            }
            else if (saveResult.IsSuccess == true)
            {// 保存成功,直接显示
                //this.View.ShowOperateResult(saveResult.OperateResult);        //成功提示,另外补充
                return;
            }
            //else if (saveResult.InteractionContext != null && saveResult.InteractionContext.Option.GetInteractionFlag().Count > 0)
            //{// 保存失败,需要用户确认问题
            //    InteractionUtil.DoInteraction(this.View, saveResult, saveOption,
            //        new Action<FormResult, IInteractionResult, OperateOption>((
            //            formResult, opResult, option) =>
            //        {
            //            // 用户确认完毕,重新调用保存处理
            //            List<ValidationErrorInfo> tempErrorList = new List<ValidationErrorInfo>();
            //            this.SaveBill(billView, option, out tempErrorList);
            //        }));
            //}
            // 保存失败,显示错误信息
            if (saveResult.IsShowMessage)
            {
                saveResult.MergeValidateErrors();
                errorList = saveResult.ValidationErrors;
                //this.View.ShowOperateResult(saveResult.OperateResult);
                return;
            }
        }

8、调用示例(代码不完整,需要根据需要编写)

                        //中间表记录唯一值
                        string uniqueId_InterTable = string.Empty;

                        //获取星空存在单据ID、中间表记录唯一值
                        object FID_Cloud = GetAomiBillFID(targetKey, ctx, mySqlDR, cloudTable, ref uniqueId_InterTable);

                        List<string> tempErrorList = new List<string>();        //错误信息


                        //导入单据
                        ImportBill(ctx, targetKey, mySqlDR, FID_Cloud, ref tempErrorList, FillBillPropertys);
                        //记录导入情况
                        SaveBillResult(tempErrorList, ref createBillNum_success, ref modifyBillNum_success, ref createBillNum_fail, ref modifyBillNum_fail, FID_Cloud);
                        

                        //如果成功,则添加到反写列表
                        if (tempErrorList.Count <= 0)
                        {
                            uniqueIdList_InterTable.Add(uniqueId_InterTable);
                        }
                        else
                        {
                            foreach (string item in tempErrorList)
                            {
                                insertSynchroFailInfoSqlList.Add(string.Format(insertSynchroFailInfoSqlModel, continueSynchroResultTableFId, uniqueId_InterTable, item));
                            }
                        }



参考资料:

1、知识分享 - 如何使用纯插件创建物料并保存,来源金蝶社区:https://vip.kingdee.com/article/24828

赞 12