2

我正在使用基于合同的 SOAP API 尝试将大约 25,000 个日记账分录行从银行系统导入单个 Acumatica GL 批次。

  1. 如果我尝试一次将所有记录添加到同一个 GL 批次,我的请求会在几个小时后超时。由于它使用相同的 GL 批处理,因此该解决方案不利用多线程。

  2. 我还尝试一次将 25000 行添加到单个 GL 批处理中,并且请求不会超时,但是在将大约 3000 条左右的记录添加到 GL 批处理后,性能速度开始显着下降。此过程需要几个小时才能运行,并且由于它使用相同的 GL 批处理,因此此解决方案不利用多线程。

  3. 我还研究了多线程,将数据导入到几个较小的 GL 批次中,每个批次有 5000 行,并且没有任何超时问题。但它仍然需要大约一个半小时才能运行。此外,客户不接受这种多批次的方法;他们希望将所有日常数据集中在一个 GL 批次中。

25,000 条记录对我来说似乎并不多,所以我想知道 Acumatica 的 API 是否不是针对单个事务中的这么多行构建的。我在代码中所做的只是通过读取文本文件来构建实体信息,然后调用 put 方法使用具有 25,000 行记录的实体创建 GL 批处理。

我已经阅读了几篇关于优化 API 的文章,但它们主要处理实体的不同实例,例如在几个不同的 GL 批次或几个不同的库存项目中。在这些情况下,多线程是一项很好的资产,因为您可以让多个线程创建多个“不同”的 GL 批次,但在更新同一个 GL 批次时,多线程没有帮助。

这是我到目前为止所读到的:

https://asiablog.acumatica.com/2016/12/optimizing-large-import.html

https://adn.acumatica.com/blog/contractapioptimization/

我在这里不知所措,所以任何指针都将不胜感激。

我期待着您的回复。

这是我的代码:

    public static void CreateMultipleLinesPerJournalEntryBatchContractTEST(MyStoreContract.DefaultSoapClient soapClient, List<JournalEntry> journalEntries)
    {
        string myModuleForBatchLookup = "GL";

        //list holding the values of all the records belonging to the batch in process
        List<JournalEntry> allBatchItems = journalEntries;
        //List used to store objects in format required by Acumatica
        List<MyStoreContract.JournalTransactionDetail> myJournalTransactionsFormatted = new List<MyStoreContract.JournalTransactionDetail>();

        try
        {


            //Creating a header and returning a batch value to be used for all  line iterations. 
            JournalEntry myHeaderJournalEntryContract = allBatchItems.First();
            string myBatchNumberToProcess = AddGLBatchHeaderContractTEST(soapClient, myHeaderJournalEntryContract);

            // Do something with then n number of items defined in processing subBatch size  or remaining items if smaller
            foreach (JournalEntry je in allBatchItems)
            {
                //Moving the items in each batch from the original unformatted list to the formatted list one at a time 
                myJournalTransactionsFormatted.Add(new MyStoreContract.JournalTransactionDetail
                {
                    BranchID = new MyStoreContract.StringValue { Value = je.Branch },
                    Account = new MyStoreContract.StringValue { Value = je.Account },
                    Subaccount = new MyStoreContract.StringValue { Value = je.Subaccount },
                    ReferenceNbr = new MyStoreContract.StringValue { Value = je.RefNumber },
                    DebitAmount = new MyStoreContract.DecimalValue { Value = je.DebitAmount },
                    CreditAmount = new MyStoreContract.DecimalValue { Value = je.CreditAmount },
                    TransactionDescription = new MyStoreContract.StringValue { Value = je.TransactionDescription },
                    UsrTransactionTime = new MyStoreContract.StringValue { Value = je.UsrTransactionTime },
                    UsrTransactionType = new MyStoreContract.StringValue { Value = je.UsrTransactionType },
                    UsrTranSequence = new MyStoreContract.StringValue { Value = je.UsrTranSequence },
                    UsrTellerID = new MyStoreContract.StringValue { Value = je.UsrTellerID }
                });
            }


            //Specify the values of a new Jornal Entry using all the collected elements from the batch(list) created 
            MyStoreContract.JournalTransaction journalToBeCreated = new MyStoreContract.JournalTransaction
            {
                //Header data and details added by list generated by loop
                BatchNbr = new MyStoreContract.StringSearch { Value = myBatchNumberToProcess }, //This is one of two lines used to lookup/search the batch needing to be updated
                Module = new MyStoreContract.StringSearch { Value = myModuleForBatchLookup }, //This is one of two lines used to lookup/search the batch needing to be updated
                Details = myJournalTransactionsFormatted.ToArray() // this is the line adding the array containing all the line details
            };

            soapClient.Put(journalToBeCreated);


            Console.WriteLine("Added " + allBatchItems.Count.ToString() + " line transactions");
            Console.WriteLine();
            Console.WriteLine("Press any key to continue");
            Console.ReadLine();



        }

        catch (Exception e)
        {
            Console.WriteLine("The following error was encountered and all entries for this batch need to be logged in error table");
            Console.WriteLine();
            Console.WriteLine(e.Message);
            Console.WriteLine();
            Console.WriteLine("Press any key to continue");
            Console.ReadLine();
        }




    }


    public static string AddGLBatchHeaderContractTEST(MyStoreContract.DefaultSoapClient soapClient, JournalEntry je)
    {

        try
        {

            //Specify the values of a new Jornal Entry Batch header
            MyStoreContract.JournalTransaction journalToBeCreated = new MyStoreContract.JournalTransaction
            {
                //Header data
                BranchID = new MyStoreContract.StringValue { Value = "PRODWHOLE" }, //This is the default branch
                TransactionDate = new MyStoreContract.DateTimeValue { Value = je.TransactionDate.AddDays(-1) }, //Reduced 1 day from the batch
                CurrencyID = new MyStoreContract.StringValue { Value = je.CurrencyCode }, //Currency to be used for the batch
                Description = new MyStoreContract.StringValue { Value = je.TransactionDescription },
                Hold = new MyStoreContract.BooleanValue { Value = true }
            };

            //Create a Journal Entry with the specified values    

            MyStoreContract.JournalTransaction newJournalTransaction = (MyStoreContract.JournalTransaction)soapClient.Put(journalToBeCreated);
            string myBatchToProcess = newJournalTransaction.BatchNbr.Value;

            return myBatchToProcess;

        }
        catch (Exception e)
        {
            Console.WriteLine("Error was caught while trying to create the header for the batch...");
            Console.WriteLine();
            Console.WriteLine(e);
            Console.WriteLine();

            return null;
        }


    }

我的遗留系统行项目的自定义类,然后我需要将其格式化为 Acumatica 的格式:

            class JournalEntry
            {

                public DateTime TransactionDate { get; set; }
                public string CurrencyCode { get; set; }
                public string Description { get; set; }
                public string Branch { get; set; }
                public string Account { get; set; }
                public string Subaccount { get; set; }
                public string RefNumber { get; set; }
                public decimal DebitAmount { get; set; }
                public decimal CreditAmount { get; set; }
                public string TransactionDescription { get; set; }
                //Added custom fields for customer
                public string UsrTellerID { get; set; }
                public string UsrTransactionType { get; set; }
                public string UsrTransactionTime { get; set; }
                public string UsrTranSequence { get; set; }
                //Adding original file data for the line
                public string FileLineData { get; set; }
        }

我尝试了下面描述的 Yuriy 的方法,但我的自定义字段没有更新。只有标准字段正在更新。我应该使用哪个命令来更新扩展(自定义)字段。请参见下面的代码:

                  //Here I create instance of GLTran
                    GLTran row = graph.GLTranModuleBatNbr.Cache.CreateInstance() as GLTran;


                    //here I get a handle to graph extension GLTranExt to be able to use the added fields.
                    var rowExt = row.GetExtension<GLTranExt>();


                    row = graph.GLTranModuleBatNbr.Insert(row);

                    graph.GLTranModuleBatNbr.Cache.SetValueExt(row, "AccountID", JE.Account);
                    graph.GLTranModuleBatNbr.Cache.SetValueExt(row, "SubID", JE.Subaccount);
                    row.TranDesc = "my line description"; 
                    row.Qty = 1.0m;
                    row.CuryDebitAmt = (JE.DebitAmount);
                    row.CuryCreditAmt = (JE.CreditAmount);
                    rowExt.UsrTellerID = "Test teller";
                    rowExt.UsrTransactionTime = "Test Transaction Time";
                    rowExt.UsrTransactionType = "Test Transaction Type";
                    rowExt.UsrTranSequence = "Test Transaction Sequence";



                    row = graph.GLTranModuleBatNbr.Update(row);

                    graph.Actions.PressSave();
4

2 回答 2

1

在销售订单的多线程导入中,我每小时有 18000 行(4 核,32Gb RAM)。所以你的 25000 与我得到的非常相似(一个销售订单有 1-6 行)。对于您提供的第二个链接,您的 API 调用的参数是什么,您的 Acumatica 实例的数量是多少(CPU、RAM、SQL Server 的参数)?

我建议您考虑水平扩展 Acumatica 并通过 SQL 分片扩展您的数据库。

编辑 如果您需要一个带有 25000 行的 GL Batch,那么我建议您采用以下解决方法:

  1. 再创建一个包含文本框和导入按钮的 Acumatica 页面。
  2. 在按钮导入按钮的代码中
    2.1 将文本框信息读取为 xml(或 JSON)
    2.2 创建 GL 图形实例
    2.3 通过图形插入所需数量(在您的情况下为 25000 )行
    2.4 调用 graph.PressSave()

  3. 将您的请求发送给 Web API,而不是发送给 GL Batch,而是发送给您创建的页面。

于 2018-12-01T21:38:30.523 回答
0

我知道这是一个老问题,但我在这里回答是为了任何偶然发现此页面的人的利益。Journal Transaction 屏幕中存在性能问题,其中创建事务的时间随着要插入的行数非线性增加。

Acumatica 支持为我们提供了一种基于定制的解决方法,显着提高了性能。我没有包含此修复程序的确切版本,但比今天(2021 年 9 月)更新的版本应该已经包含此修复程序。

自定义修复:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using PX.Api;
using PX.Data;
using PX.Common;
using PX.Objects.Common;
using PX.Objects.Common.Extensions;
using PX.Objects.CS;
using PX.Objects.CM;
using PX.Objects.CA;
using PX.Objects.Common.Bql;
using PX.Objects.Common.GraphExtensions.Abstract;
using PX.Objects.Common.GraphExtensions.Abstract.DAC;
using PX.Objects.Common.GraphExtensions.Abstract.Mapping;
using PX.Objects.GL.DAC;
using PX.Objects.GL.FinPeriods;
using PX.Objects.GL.JournalEntryState;
using PX.Objects.GL.JournalEntryState.PartiallyEditable;
using PX.Objects.GL.Overrides.PostGraph;
using PX.Objects.GL.Reclassification.UI;
using PX.Objects.PM;
using PX.Objects.TX;
using PX.Objects.Common.Tools;
using PX.Objects.GL.DAC.Abstract;
using PX.Objects.Common.EntityInUse;
using PX.Objects.GL.FinPeriods.TableDefinition;
using PX.Data.SQLTree;
using PX.Objects.CR;
using PX.Data.BQL.Fluent;
using PX.Data.BQL;
using PX.Objects;
using PX.Objects.GL;

namespace PX.Objects.GL
{
  public class JournalEntry_Extension : PXGraphExtension<JournalEntry>
  {
    public delegate void PopulateSubDescrDelegate(PXCache sender, GLTran Row, Boolean ExternalCall);

    [PXOverride]
    public void PopulateSubDescr(PXCache sender, GLTran Row, Boolean ExternalCall, PopulateSubDescrDelegate baseMethod)
    {
      if (Base.IsImport || Base.IsExport || Base.IsContractBasedAPI)
      {
          return;
      }
      
      baseMethod(sender,Row,ExternalCall);
    }  
  }
}
于 2021-09-23T14:33:17.483 回答