Part 9.5 - What About PostLoad()?

What is PostLoad and how can we use it on Data Entities?

PostLoad is a kernel method that is executed when any record is loaded in Finance and Operations plus other versions of the product like AX 2012 and AX 2009. This can be used on tables, views, data entities and even maps. However, we're going to focus on data entities for this article. PostLoad() is used to add values on unmapped fields on a data entity that don't use a calculated field / sub-select to fetch its data. These let you use X++ to add in just about any type of calculation for a field that gets executed whenever a record buffer for that entity is scoped. However, PostLoad() can be executed more than once and may be called when you don't have a saved record so any logic you add has to be execution safe regardless of the state of the record when PostLoad() gets called. Additionally, because we're adding work onto the load of each record, we have to pay attention to what we're doing or we can have a negative impact on performance.

The Tests

In order to demostrate how PostLoad can kill performance, I setup a few tests. First, we'll use the standard Sales Order Header V2 entity as the baseline. Next, I copied that entity and created a new one with a very minor PostLoad() workload. This will calculate the number of SO lines and display it on the SO Header for this entity. Next, I copied Sales Order Header V2 to make another entity with larger PostLoad() footprint. This entity on post load will calculate the number of SO Lines, invoices, packing slips, picking lists, the value of the SO ( in dollars ) and the total quantity for all lines on the SO. We will use each entity to fetch 100 random SO Headers and record the amount of time to execute each request.

Simple PostLoad()

In our first entity for a simple PostLoad(), this is the code for its PostLoad:

    public void postLoad()
    {
        SalesLine SalesLine;
        super();

        if (this.RecId)
        {
            select count(recId) from SalesLine
                where SalesLine.SalesId == this.SalesOrderNumber;

            this.NumberOfSalesOrderLines = SalesLine.RecId;
        
        }
    }

Complicated PostLoad

In our second entity, it's PostLoad looks like this:

    public void postLoad()
    {
        SalesLine SalesLine;
        CustPackingSlipJour CustPackingSlipJour;
        CustInvoiceJour CustInvoiceJour;
        WMSPickingRouteLink WMSPickingRouteLink;

        this.NumberOfSalesOrderLines = 0;
        this.NumberOfInvoices = 0;
        this.NumberOfPackingSlips = 0;
        this.NumberOfPickingLists = 0;
        this.SalesOrderValue = 0;
        this.SalesOrderQty = 0;
       
        if (this.RecId)
        {
            select count(recId), sum(SalesQty) from SalesLine
                where SalesLine.SalesId == this.SalesOrderNumber;

            select count(RecId) from custPackingSlipJour
                where CustPackingSlipJour.SalesId == this.SalesOrderNumber;

            select count(RecId) from CustInvoiceJour
                where CustInvoiceJour.SalesId == this.SalesOrderNumber;

            select count(RecId) from WMSPickingRouteLink
                where WMSPickingRouteLink.origInventTransRefId == this.SalesOrderNumber;

            this.NumberOfSalesOrderLines = int642int(SalesLine.RecId,false);
            //add sales qty, sales value, count of picking list, count of pack slips, count of invoice
            try
            {
                //this appears to fail for some locales and didn't want to debug why
                SalesOrderTotals SalesOrderTotals = SalesOrderTotals::calculateForSalesOrder(this.SalesOrderNumber, DateTimeUtil::utcNow());
                
                this.SalesOrderValue = int642int(SalesOrderTotals.OrderTotalAmount,false);
            }
            catch
            {
                ; //honey badger don't care
            }
        
            this.NumberOfInvoices = int642int(CustInvoiceJour.RecId,false);
            this.NumberOfPackingSlips = int642int(CustPackingSlipJour.RecId,false);
            this.NumberOfPickingLists = int642int(WMSPickingRouteLink.RecId,false);

            this.SalesOrderQty = int642int(SalesLine.SalesQty,false);
        
        }
        super();
    }

The Results

Below are the result of 100 random reads from each data entity.

Our "Read" value represents the standard OOTB entity for Sales Order Headers (V2). Next, "ReadPostLoad" has the simple PostLoad() example form above. "ReadPostLoadExtended" has the PostLoad() for the complicated PostLoad method example above. Since this is at a per record metric, even the difference between Read and ReadPostLoad is fairly huge if you consider that this entity may be handling tens of thousands of records in a day. You can also see the more code we add in PostLoad, the longer it takes to process the request with the ReadPostLoadExtended .

Key Takeaways

  • PostLoad can help add in some data for data entities at a performance cost
  • Performance goes down related to the amount of work being performed
  • Don't ask PostLoad to do too much as it will have a detrimental affect on the overall data entity performance
  • PostLoad has to record state safe so it may be called on a new record that hasn't been written to disk yet

All code for this can be found at https://github.com/NathanClouseAX/AAXDataEntityPerfTest/tree/main/Projec....

All visuals can be found at https://github.com/NathanClouseAX/AAXDataEntityPerfTest/tree/main/Projec...

 

Blog Main Tag: