3.4 Limiting Field Lists
Most of the X++ select statements in
Dynamics AX retrieve all fields on a record, although the values in
only a few of the fields are actually used. The main reason for this
coding style is that the Dynamics AX application runtime doesn’t report
compile-time or run-time errors if a field on a record buffer is
accessed and it hasn’t been retrieved from the database. The following
X++ code, which selects only the AccountNum field from the CustTable table but evaluates the value of the CreditRating field and sets the CreditMax field, won’t fail because the application runtime doesn’t detect that the fields haven’t been selected.
static void UpdateCreditMax(Args _args) { CustTable custTable; ; ttsbegin; while select forupdate accountNum from custTable { if (custTable.CreditRating == '') { custTable.CreditMax = custTable.CreditMax + 1000; custTable.update(); } } ttscommit; }
|
This code updates all CustTable records to a CreditMax value of 1000, regardless of the previous value in the database for the CreditRating and CreditMax fields. Adding the CreditRating and CreditMax fields to the field list of the select statement might not solve the problem because the application logic could still update other fields incorrectly because the update method on the table could be evaluating and setting other fields on the same record.
Important
You could, of course, examine the update
method for other fields accessed in the method and then select these
fields as well, but new problems would soon surface. For example, if
you customize the update method to
include application logic that uses additional fields, you might not be
aware that the X++ code in the preceding example also needs to be
customized. |
Limiting
the field list when selecting records does result in a performance
gain, however, because less data is retrieved from the database and
sent to the AOS. The gain is even bigger if you can retrieve the fields
by using the indexes without lookup of the values on the table. This
performance gain can be experienced and the select
statements written safely when you use the retrieved data within a
controlled scope, such as a single method. The record buffer must be
declared locally and not parsed to other methods as a parameter. Any
developer customizing the X++ code can easily see that only a few
fields are selected and act accordingly.
To
truly benefit from a limited field list, you must understand that the
Dynamics AX application runtime sometimes automatically adds extra
fields to the field list before parsing a statement to the database.
In the following X++ code, you can see how the application runtime adds additional fields and how to optimize some select statements. The code calculates the total balance for all customers in customer group ‘20’ and converts it into the company’s currency unit. The amountCur2MST method converts the value in the currency specified by the currencyCode field to the company’s monetary unit.
static void BalanceMST(Args _args) { CustTable custTable; CustTrans custTrans; AmountMST balanceAmountMST = 0; ; while select custTable where custTable.CustGroup == '20' join custTrans where custTrans.AccountNum == custTable.AccountNum { balanceAmountMST += Currency::amountCur2MST(custTrans.AmountCur, custTrans.CurrencyCode); } }
|
When the select statement is parsed to the database, it retrieves all CustTable and CustTrans record fields, even though only the AmountCur and CurrencyCode fields on the CustTrans table are used. The result is the retrieval of more than 100 fields from the database.
You can optimize the field list simply by selecting the AmountCur and CurrencyCode fields from CustTrans and, for example, only the AccountNum field from CustTable, as shown in the following code.
static void BalanceMST(Args _args)
{ CustTable custTable; CustTrans custTrans; AmountMST balanceAmountMST = 0; ; while select AccountNum from custTable where custTable.CustGroup == '20' join AmountCur, CurrencyCode from custTrans where custTrans.AccountNum == custTable.AccountNum { balanceAmountMST += Currency::amountCur2MST(custTrans.AmountCur, custTrans.CurrencyCode); } }
|
As
explained earlier, the application runtime expands the field list from
the three fields shown in the preceding X++ code example to five fields
because it adds the fields used when updating the records. These fields
are added even though neither the forupdate keyword nor any of the specific concurrency model keywords are applied to the statement. The statement parsed to the database starts as shown in the following example, in which the RECID column is added for both tables.
SELECT A.ACCOUNTNUM,A.RECID,B.AMOUNTCUR,B.CURRENCYCODE,B.RECID FROM CUSTTABLE A,CUSTTRANS B
|
To prevent retrieval of any CustTable fields, you can rewrite the select statement to use the exists join operator, as shown here.
static void BalanceMST(Args _args)
{ CustTable custTable; CustTrans custTrans; AmountMST balanceAmountMST = 0; ; while select AmountCur, CurrencyCode from custTrans exists join custTable where custTable.CustGroup == '20' && custTable.AccountNum == custTrans.AccountNum { balanceAmountMST += Currency::amountCur2MST(custTrans.AmountCur, custTrans.CurrencyCode); } }
|
This code retrieves only three fields (AmountCur, CurrencyCode, and RecId) from the CustTrans table and none from the CustTable table.
In some situations, however, it might not be possible to rewrite the statement to use exists join. In such cases, including only TableId
as a field in the field list prevents the retrieval of any fields from
the table. The original example is modified as follows to include the TableId field.
static void BalanceMST(Args _args) { CustTable custTable; CustTrans custTrans; AmountMST balanceAmountMST = 0; ; while select tableid from custTable where custTable.CustGroup == '20' join AmountCur, CurrencyCode from custTrans where custTrans.AccountNum == custTable.AccountNum { balanceAmountMST += Currency::amountCur2MST(custTrans.AmountCur, custTrans.CurrencyCode); } }
|
This code causes the application runtime to parse a select statement to the database with the following field list.
SELECT B.AMOUNTCUR,B.CURRENCYCODE,B.RECID FROM CUSTTABLE A,CUSTTRANS B
|
If you rewrite the select statement to use exists join or include only TableId as a field, the select
statement sent to the database retrieves just three fields instead of
more than 100. As you can see, you can substantially improve your
application’s performance just by rewriting queries to retrieve only
the necessary fields.
3.5 Field Justification
Dynamics
AX supports left and right justification of extended data types. With
our current releases, nearly all extended data types are left justified
to reduce the impact of space consumption because of double and triple
byte storage as a result of Unicode enablement. Left justifying also
helps performance by increasing the speed of access through indexes.
Where
sorting is critical, you can use right justification. This has to be an
exception as is clearly evident in our usage within the application
layers we ship.
3.6 Other Performance Considerations
You
can further improve transactional performance by giving more thought to
the design of your application logic. For example, ensuring that
various tables and records are always modified in the same order helps
prevent deadlocks and ensuing retries. Spending time preparing the
transactions before starting a transaction scope to make it as brief as
possible can reduce the locking scope and resulting blocking, and
ultimately improve the concurrency of the transactions. Database design
factors, such as index design and use, are also important.