Wednesday, December 22, 2010

Cleanup cache .auc file

If you wants to delete the local cache, do following steps:

1. Stop AOS.
2. Delete the .auc file from users\\Appdata\ folder
3. Restart AOS.

Run SalesConfirmation report through code

Here is an example to print the sales confirmation report through code:

static void printSalesConfirmationReport(Args _args)
    Args args;
    ReportRun reportRun;
    SalesFormLetter salesFormLetter;
    PrintJobSettings printJobSettings;
    CustConfirmJour custConfirmJour;
    RecordSortedList list = new RecordSortedList(55);
    SalesTable salesTableUpdate;

    SELECT firstonly custConfirmJour order by ConfirmID desc
    where custConfirmJour.SalesId ==   salesTable.SalesId ;
    args = new Args(ReportStr(SalesConfirm));
    printJobSettings = new PrintJobSettings();

    salesFormLetter = new SalesFormLetter_Confirm(true);
    reportRun = new ReportRun(args);

Wednesday, December 8, 2010

Dynamics AX Business Intelligence

There is a new blog from Microsoft, focusing specifically on Dynamics AX BI. The blog can be found here.:

Dynamics AX Business Intelligence

AX SSRS Reporting Concepts
ScreenCast - AX SSRS Reporting Concepts on YouTube

Dilemma of delete actions- Restricted/Casecade+Restricted

To explain the difference b/w Restricted and Cascade+Restricted delete action, consider the following example:
Let's say you have tables CarTable and RentalTable, there is a one-to-many relationship between CarTable and RentalTable. Then if a record in CarTable is deleted and the delete action is:

None: No action on related records in RentalTable.

Cascade: All records in RentalTable related to the record being deleted in CarTable will also be deleted.

Restricted: The user will get a warning saying that the record in the CarTable cannot be deleted because transactions exist in table RentalTable. The user will be unable to delete the record in CarTable if one or more related records exist in the RentalTable.

Cascade + Restricted: This option is used when deleting records through more than two levels. Let's say another table existed that had a cascade delete action against CarTable and the CarTable had a Cascade + Restricted delete action against RentalTable. If the record in the top level table was about to be deleted it would also delete the related records in CarTable. In turn, all the records in RentalTable related to the records being deleted from CarTable would also be deleted (Cascade behaviour). If only a record in CarTable was about to be deleted, the user would get the same message when using the Restricted method (Restricted behaviour).

Thursday, November 25, 2010

Update changes to Production/live environment

Most companies have 3 different Dynamics AX environments:

- Development environment
- Test/Acceptation environment
- Production/live environment.

When a customer wants to have changes to their production/live environment, the describe some kind of document with requirements. A developer will implement the requirements in their Development environment. When he is finished someone has to copy the changes to the test/acceptation environment. The customer will test the changes and when he accepts them, the changes have to be copied to the production environment.

This coping of changed code can be done in 2 ways:

- Xpo file (with label files).
- A var layer files (with label files).

  1. Copy the delivered Xpo or layer and label files to your delivery archive directory. (Note use the delivery date in the folder name). 
  2. Create in the archive directory a directory backup.
  3. Stop all batch servers.
  4. Stop all AOS. 
  5. Remove all cache files (del/s *.aoc). 
  6. Create a database backup and copy this to the archive backup directory.(point 2)
  7. Copy the axVar.aod, axCus.aod and axUsr.aod from your test environment to the archive backup directory and to the “old” directory in your application tree. (So you can do layer compare afterwards).
  8. Copy the label files into your application tree.
  9. Start an AOS with a different name and/or Port number. This new AOS should only be used for administration purpose. The reason for this dedicated AOS is that upgrading AX can conflict to the stability of your environment. The end user can experience the next thing: a.
  10. a. Stack traces on the screen.
    b. Missing labels.
    c. Lost or corrupted data.
    d. An occupied phone line of the Dynamics AX administrator, because  everyone is phoning him that he or she can’t do her work.
  11. Import the Xpo file and press the recompile button if errors occur. If the error a stays repeat this step one more time. 
  12. Administrator/Periodic/SQL Administrator use check/synchronize to update the data model. Run it a second time in diagnostic mode only, to be sure it was ok.
  13. Administrator/Periodic/Compile application. (Compiling from the AOT will not work when no developers license is used)
  14. Stop the administration AOS.
  15. Start live/production AOS.
  16. Start all Axapta batch servers.

Wednesday, November 24, 2010

Using while select firstonly to avoid validations in Dynamics Ax

When you execute a select statement to retrieve a single record most of us right the query first. This is followed by a validation to ensure that the query has returned a record. we make it simpler If we right it the way mentioned below

static void WhileSelectInsteadoFSelect(Args _args)
    CustTable custTable;

    //-----General way---------
    // fetch the record first
    select firstonly forupdate custTable;

    //add an additional validation
    if (custTable.RecId)
         custTable.Memo = 'updated value';

    //prevents the extra 'if' check
    while select firstonly forupdate custTable
        custTable.Memo = 'updated value';

    //can also be used for simple readonly
    while select firstonly custTable

Sunday, November 21, 2010

Intelligent Data Management Framework For Microsoft Dynamics AX

From the IDMF page.:

"The Intelligent Data Management Framework for Microsoft Dynamics® AX helps administrators optimize Dynamics AX database layout by providing functionality to manage key database layout techniques, by way of intuitive Archiving and Purging capabilities.

The framework provides the platform for reduction in database size by purging and archiving of transactions records from sets of relating entities, while maintaining the consistency and integrity of production data. The Intelligent Data Management Framework provides customers and partners the ability to identify and discover related entities based on the Microsoft Dynamics AX metadata and to determine the purging or archiving criteria for tables.

The Intelligent Data Management Framework also analyzes the Production database to determine usage patterns and assesses the health of the Microsoft Dynamics AX Application. This Framework is compatible with all supported versions of Dynamics AX. The Intelligent Datat Management Framework for Microsoft Dynamics AX is a benefit of staying enrolled in a Business Ready Enhancement plan."

The following is a direct link to a presentation put on by Tao Wang Principal Development Manager Dynamics AX performance, at the AXUG Summit this year: TEC01 - The New Intelligence Data Management (IDM) Tool

Thursday, October 28, 2010

Query - A general approach

Query can be seen as a class interface for creating select statements.

Use a query in place of a select statement when the structure of the "select" (query) is not known until run time, or when the user should be able to specify the ranges.
In other situations, use a select statement. Select statements are often easier to read, and they are much more compile-time stable.

Queries are embedded in Forms, Reports and RunBase jobs and should always be used with these application objects, unless there are good reasons not to.

Queries are very flexible. Everything can be specified at run time, but specify as much as possible before run time. 

Create queries in the Application Object Tree (AOT) where possible, instead of building them in code.

Use intrinsic functions everywhere possible to enable compile-time checking of code.

The range of a query can be specified under program control by using two different techniques, the normal one and the query range value expression. Both take a string as an argument that is not evaluated until run time. The string must fulfill certain syntax restrictions and be as compile-time stable as possible.

Query Range value:

Always use the SysQuery::value method when you are programmatically assigning an atomic value to a query range.

Always use the SysQuery::range method when you are programmatically assigning a value range to a query range. The range method will supply the needed ".." operators.

Always use the SysQuery::valueEmptyString method when you want to have a range which must have a blank value.

Always use the SysQuery::valueUnlimited method when you want to have a completely open range.

Query Range value Expression:

Only use the query range value expressions if the normal query range values cannot be used.

Be aware that query range value expressions are evaluated only at run time (so they are not checked at compile time).

Because the contents of the Query range value expressions should look like X++, you should be careful to format the different data types correctly. Use the strFmt system function or queryValue/queryRange for this. queryValue and queryRange are methods on the Global class.

Place the expression in a range that is defined on the most relevant of the involved fields.

Tuesday, October 26, 2010

How to put Ax 2009 machine back to a clean state (remove the usr layer)

1. Stop the AOS (default service name is AOS50$01 for AX5).

2. Delete the user layer aod file (…\Microsoft Dynamics AX\50\Application\Appl\AXDB_App\axusr.aod) and *.aoi files.

3. Delete the metadata file in client %userprofile%\AppData\*.auc

4. Start the AOS.

5. Run AX client and do a synchronize on the Data Dictionary node on the AOT tree.

Wednesday, September 15, 2010

Axapta AOS cluster

Setting up the Axapta Cluster environment

The Axapta Object Server environment offers a load balancing feature - called the Axapta Object Server Cluster. A number of object servers can be assigned to the same Axapta Object Server Cluster. A client is configured to connect to a Cluster. It will then choose the server with the lowest number of on-line users. With this approach the Axapta Object Server Environment can also be made fault tolerant. If an Object Server fails, a client can immediately connect to one of the other servers in the same cluster.

Axapta object server cluster

The Axapta Object Server cluster environment is build to provide load balancing and fall back. An Axapta Object Server can be specified to belong to a specific cluster. If the same cluster name is given for a number of object servers they all belong to the same cluster. A user can be specified to connect to a given cluster.

Suppose a number of servers are set up in the same cluster - named AOSC. They should all use the same application and database, and in other aspects have the same settings. All clients connecting to the AOSC should be specified to search for servers belonging to this cluster; otherwise the load balancing mechanism will not work properly. A client starting up against an Axapta Object Server Cluster will start out by broadcasting an UDP package requesting Axapta Object Servers fulfilling names and IP server masks given in the client settings to respond. It is therefore vital that firewalls allow such packages to pass through. The client will receive answers from all Object Servers with information on object server name and the name of the instance plus the number of clients connected to that instance. The client disregards information from servers not belonging to the requested cluster. The client then chooses one of the instances with the lowest number of clients.

Since a client automatically can choose from all servers in a cluster one or more Object Servers can be down without changing the connection procedures. However performance might suffer severely due to higher load on the fewer servers.

Setting up the server

On the server manager select the server of interest and click settings. In the advanced field write -cluster=. The cluster name can be up to 10 characters long.

Setting up the client

In the configuration tool choose the configuration of interest. In the advanced field write -cluster=. The client setting for connecting to an object server depends on two situations. The first situation being both client and Axapta Object Servers being in the same subnet. The second being client and Axapta Object Servers belong to different subnets.

Recall the settings in the configuration utility for connecting to an object server:

* Axapta Object Server Mask
* Axapta Object Server host names
* Axapta Object Server IP Address masks

When the client and Object Servers belong to the same subnet the above three masks can be left blank.

When the client and Object Servers belong to different subnets the IP Address masks should contain the mask for the subnet the Object Servers belong to. An example could be, where the last 255 specifies that the search is done among servers in the subnet 192.88.253.*.

Note: If client at any time receives only service response from Object Servers within the same cluster it will automatically choose the least loaded of these servers without having to specify the cluster name in the
client configuration. This means that if your installation is one single Axapta cluster, there is no need to change the existing configuration with respect to cluster name. Just ensure that all relevant Object Servers are
located by client configurations network setup.

Thursday, August 5, 2010

Creating New Inventory Dimension - Storage

AX has 8 standard inventory dimension here is the classification of these 8 dimensions

Inventory Item Dimensions
  1. Size
  2. Colour
  3. Configuration
Inventory Storage Dimensions
  1. Batch
  2. Serial
  3. Warehouse
  4. Location
  5. Pallet
Below is the list of objects that needs to be created or modified while creating a new inventory storage dimension. Please note that the list is not exhaustive enough but contains most of the objects that needs to be modified. Let us assume the name of the new dimension would be 'Test'
xtended Data Types

Following is the table specifying the EDTs that one may need to create for new storage dimension. Reference EDT section specifies which EDT to look at while creating new EDT.

EDT Name = InventTestId
Label = Test
Extends = SysGroup
Type = String
Reference EDT = InventLocationId


New table 'InventTest' : Look at the table InventLocation for reference and create a new table InventTest. Do not forget to create the field groups, indexes, properties similar to InventLocation. You can duplicate this table and change the required fields, methods, fields groups, indexes, propeties etc.

Table InventParameters : A new method 'numRefInventTestId' needs to be created. Take method 'numRefInventSerialId' for an example while creating this method.

Table InventDim : You will have to create a new field 'InventTestId'. Add this field to field groups, 'AutoReport', 'InventoryDimensions'. Create a new index 'TestIdx' with this field, Add this field InvenyTesId to index 'DimIdx'. Modify the following methods, take cue from code for other dimensions and add the relevant code. Methods are: 'dim2dimParm', 'dimParm2dim', 'emptyDimField', 'findDim', 'findOrCreate', 'formQueryAddDynaLink', 'isFieldIdItemDim', 'isFieldIdTransferReceiptMergable', 'isFieldIdTransferTransitReceiptMergable'.

Table InventDimParm : Add a new field 'InventTestIdFlag'. Add this field to following field groups: 'AutoReport', 'Fixedview', 'View'.

Table InventSum : Add a new method named 'inventTestId'. Take cues from 'inventSerialId' method in the table.

Table InventSumDateTrans : You will have to create a new field 'InventTestId'. Add this field to field groups, 'AutoReport', 'InventoryDimension'. Modify following methods: 'initFromInventDim', 'inventDim'.

Table InventSumDeltaDim : You will have to create two new fields named 'InventTestId', 'InventTestIdFlag'. Add the 'InventTestId' field to following groups : 'AutoLookup', 'InventDim'. Add 'InventTestIdFlag' field to following groups: 'InventDimFlags'. Modify following methods: 'initFromInventDim', 'initFromInventDimParm', 'initInventDim', 'initInventDimParm'.

Table InterCompanyInventDim : You will have to create a new field 'InventTestId'. Add this field to field groups: 'AutoReport', 'InventoryDimensions', 'TmpInventDimensions'. Add the field to following indexes: 'DimIdx'. Modify following methods: 'fromInventDim', 'toInventDim', 'dim2dimParm', 'dimParm2dim', 'formQueryAddLink'.

Table PBATreeInventDim : You will have to create two new fields named 'InventTestId', 'InventTestIdVar'. Add the 'InventTestId' field to following groups : 'AutoReport', 'AllFields', 'InventoryDimensions'. Add 'InventTestIdVar' field to following field group: 'InventoryDimensions'. Add a new method named 'inventTestId'. Take cues from 'inventSerialId' method in the table.

Following is the list of macros that may need to be modified  
  1. InventDimSelect 
  2. InventDimJoin 
  3. InventDimGroupAllFields 
  4. InventDimExistsJoin


New class AxInventTest : Run the class AxGenerateAxBC and select the table InventTest. This will create new AxBC class for InventTest table and you will have to resolve some errors.

Class NumberSeqReference_Inventory : The method 'loadModule' has to be modified to create a reference for 'Test'.

Class InventDimRenameValue : Add a method 'newInventTest'. Take cue from method 'newInventSerial' for definition of this new method.

Class InventDimTracking : Modify method 'initFromArgs'. Take an example of any of the inventory dimensions for changes.

Class AxInventDim : Add parm and set methods for InventTestId field. Take example of parmInventSerialId and setInventSerialId. Modify method setTableFields to make a call to setInventTestId method.

AxInventDim_PriceDiscTable : Add method setInventTestId. Take example of setInventSerialId for definition of this method.

Class InventDimFixedClass : Add following methods : 'parmInventTestIdFlag' - Take example from parmInventSerialIdFlag method and 'inventTestIdFlag' - Take example from inventSerialId flag method. Create a macro for InventTestId in ClassDeclaration (look at declarations for other dimensions). Modify method 'fieldId2Idx'.

Class TradeInterCompany : Modify method 'createTmpInventSum'.

Class PBALib : Modify method 'createInventDim'.

Apart from these classes there are many more classes that may need modifications like InventMovement, InventUpd and its child classes. InventOnhand and related classes etc. If you want to know more you can search for keyword 'InventSerial' in the classes node and find out.


Following new forms need to be created

InventTest : Similar to InventSerial.
InvenTestIdLookup : Similar to InventSerialIdLookup

Tuesday, July 6, 2010

Check Journal Balance - When you meet a problem of "do not balance", this break point will be very helpful.



Invoke Scenario:

When there is some problem about imbalance, this function will be very helpful to track why it is imbalance.

Operation Instance:

General ledger -> Journals -> General journal -> Create a new line -> Click Line -> add lines for journal -> Click button post -> post.

Explanation of codes:
private void updateTotals(LedgerTrans _ledgerTrans)
LedgerTrans ledgerTransTotals;
if (transDateTotals.find(ledgerTransTotals)) //line 13

ledgerTransTotals.AmountCur += _ledgerTrans.AmountCur;
ledgerTransTotals.AmountMST += _ledgerTrans.AmountMST; //line 16
ledgerTransTotals.AmountMSTSecond += _ledgerTrans.AmountMSTSecond;
ledgerTransTotals.Qty += _ledgerTrans.Qty;
transDateTotals.ins(ledgerTransTotals, true);

else //line 21
“ledgerTransTotals” is used to accumulate all the transactions. If the transaction is the first transaction needs to be checked, it goes into line 13. Otherwise, it goes into line 21. The ledgerTransTotals.AmountMST at line 16 should be 0 when the last transaction has been added, or it will not be posted. And you will receive a infolog “The trasaction on voucher *** do not balance”

Post Journal - Illustrate the structure of LedgerJournalcheckPost\postJournal. It is called by most journals.

Object:  Classes\LedgerJournalCheckPost\postJournal

Invoke Scenario:

Most of the journals (including “General ledger”, “Payment journal”, “Invoice journal”, “Invoice register”, “Invoice approval journal”, etc.) will call this method to deal with the posting staff.

Operation Instance:

General ledger -> Journals -> General journal -> Create a new line -> Click Line -> add lines for journal -> Click button post -> post.

Explanation of codes:

while select ledgerJournalTrans order by Voucher, AccountType, RecId //line 99

where ledgerJournalTrans.JournalNum == ledgerJournalTable.JournalNum



while (ledgerJournalTrans.AccountNum


// line 316


if (!this.postTrans(……))


ledgerJournalTrans = this.ledgerJournalTransOffset(ledgerJournalTrans, // line357







if (interCompanyCompanies.elements() > 0) //line 400


interCompanyCompaniesEnumerator = interCompanyCompanies.getEnumerator();

while (interCompanyCompaniesEnumerator.moveNext())
if (interCompanyJournalIds.exists(interCompanyCompaniesEnumerator.current()))
ok = this.postJournalInterCompany(……);
Instance: (General journal Form)

GL -> Journals -> General journals -> lines
Add two lines as following:
Account Debit Credit Offset account

11010 1000 12060(intercompany)

21125 200 40112

Line 99: The while loop contains all the lines created in the journal form. In this instance, the while loop will be execute twice. One is for “11010, Offset 12060”, another is for “21125, Offset 40112”.

Line 316: This while loop will be execute twice at most. One is for the Current line account, another is for the offset account. In the end of this loop line357, the ledgerJournalTrans will be changed to its offset account.

Line 400: The codes within the ‘if’ condition is used to post the legertrans for intercompany. In this instance, the while loop inside the ‘if’ condition will be executed once. Because there is only one intercompany record 12060.

Friday, June 25, 2010

Upgrade from AX3.0, AX4.0 to AX 2009

The steps below provide a high-level overview of the tasks that you must complete to upgrade from Microsoft Dynamics AX 3.0 to Microsoft Dynamics AX 2009.

Back up your existing database and application files.

Import two .xpo files from the installation media to assist with data upgrade.

UpgradeColumnList.xpo, for 32-bit to 64-bit RecId field conversion.

LeftJustified.xpo, for removing any trailing spaces from fields.

Note: To help improve performance, you can apply the LeftJustified.xpo on the database that you create in step 4 after you’ve used the Microsoft Dynamics AX DB Upgrade Preparation tool but before you start the Microsoft Dynamics AX 2009 AOS.

(Optional) To help improve performance, remove all user data and logs of Microsoft Dynamics AX 3.0. For example, clean up the SysDatabaseLog table.

Create an empty database for Microsoft Dynamics AX 2009 in SQL Server 2005.

(Optional) To help improve performance, set initial data and log file sizes so that they don’t increase while you perform the data upgrade process.

(Optional) To help improve performance, set the recovery model to Simple for the Microsoft Dynamics AX 2009 Database.

Run AXDBUpgrade.exe (The Microsoft Dynamics AX DB Upgrade Preparation tool). Note: To help improve performance, you can run this tool in Multithreaded mode. For example, to run this tool in 10 threads, enter AxDbUpgrade.exe P/10 at a command prompt.

(Optional) Apply the LeftJustify file imported in step 2 to the Microsoft Dynamics AX 2009 database created in step 4.

Back up your Microsoft Dynamics AX database. Your database is ready to be upgraded.
Run the Microsoft Dynamics AX 2009 Setup file from the installation media. During installation, select the database that you created in step 4.

Copy your upgraded customized file into the correct application directory.

Start the AOS.

Start the Microsoft Dynamics AX 2009 client. The Upgrade checklist is displayed automatically.

Complete the steps in the Upgrade checklist to finish upgrading.

The steps below provide a high-level overview of the tasks that you must complete to upgrade from Microsoft Dynamics AX 4.0 to Microsoft Dynamics AX 2009.

Back up your existing database and application files (AX 4.0 database and (*.aod, *.ahd, *.ald, *.add, *.khd files from Dynamics AX 4.0\Application\Appl folder; copy these files from configuration that you wants to be upgrade).

Before you upgrade, you must execute the following SQL script on your AX 4.0 database: 



























Run the Microsoft Dynamics AX 2009 Setup file from the installation media. During installation, select your existing Microsoft Dynamics AX database (don't start AOS service)

Go to AX instance location that you installed in last step, normally this location is in \Microsoft Dynamics\5.0\Application\Appl\ folder, create new folder named 'Old' and copy SYS and GLS (*.aod, *.ahd, *.ald, *.add, *.khd) files from AX 4.0 backup to this Old folder.

Back to the location \Microsoft Dynamics\5.0\Application\Appl\ and paste all AX 4.0 application files (*.aod, *.ahd, *.ald, *.add, *.khd ) from all layers above the LOS layer ( BUS, VAR, CUS, USR, and respective patch layers) to this location. Delete index file (axapd.aoi - if found) from this directory.

Start the Microsoft Dynamics AX 2009 client. The Upgrade checklist is displayed automatically.

Complete the steps in the Upgrade checklist to finish upgrading.

Tuesday, June 22, 2010

close* methods on a form

There are “only” 5 ways to close a form:
1. Close - close the form
2. CloseOK – close the form, and set the OK flag – called by the commandbutton: Ok
3. CloseCancel – close the form, and set the Cancel flag – called by the commandbutton: Cancel
4. CloseSelectRecord – close the lookup form, and set return record
5. CloseSelect – close the lookup form, and set return value

The methods in past-tense are used to determine if or how a form was closed:
6. Closed – The form is no longer open
7. ClosedOK – The form was closed by the user clicking ‘OK’
8. ClosedCancel – The form was closed by the user clicking ‘Cancel’

And canClose() is called before any of the close methods get called.

Monday, June 7, 2010

The New Dynamics AX 2011 X++ Editor

New features have already been added to the next version of Dynamics AX, Microsoft Dynamics AX 2011.
Some of the new features include:

1. Ability to see lines in code
2.Improved IntelliSense features (all possibilities will appear once you type)
3.Great ability to select words in editor without selecting the line from the beginning
4.More colors
5.Ability to check Labels value without going to the Label Editor
6... and many more.

Wednesday, May 26, 2010

Generic SysTableLookup Method

It is often needed to write a custom lookup method and the SysTableLookup class can be useful to create lookups from code. However the following method uses the SysTableLookup in the background but can be called easier.

When using the SysTableLookup class, for most of the simple lookups (1 datasource table) it is alway the same. You need the following :

TableId, LookupControl, LookupFields, ReturnFields, SortFields and sometimes the use of a tmpTable.

Now following method is a generic method to user the SysTableLookup class :
public static void doLookup(TableId             _tableId,
                            Container           _lookupFields,
                            Container           _sortFields,
                            FormStringControl   _control,
                            FieldId             _returnItemFieldNum,
                            Map                 _queryRanges    = null,
                            Boolean             _useTmpTable = false,
                            Common              _tmpBuffer = null
    SysTableLookup          sysTableLookup  = SysTableLookup::newParameters(_tableId, _control);
    Query                   query           = new Query();
    QueryBuildDataSource    qbds;
    int                     i;
    fieldId                 lookupFieldId;
    for(i=1;i <= conlen(_lookupFields);i++)
        lookupFieldId = conPeek(_lookupFields, i);
        if(lookupFieldId == _returnItemFieldNum)
            sysTableLookup.addLookupfield(lookupFieldId, true);
    qbds = query.addDataSource(_tableId);
    for(i=1;i <= conlen(_sortFields);i++)
        qbds.addSortField(conPeek(_sortFields, i));
        rangeEnumerator = _queryRanges.getEnumerator();
        while (rangeEnumerator.moveNext())

Now when you want to create a lookup you can do it easier by doing the following :
public void lookup()
    Container   fieldNums       = [FieldNum(CustTable, AccountNum), FieldNum(CustTable, Name)];
    Container   sortFields      = [FieldNum(CustTable, AccountNum)];
    FieldId     returnFieldId   =  FieldNum(CustTable, AccountNum);
    Map         queryRanges     = new Map(Types::Integer, Types::String);
    queryRanges.insert(FieldNum(CustTable, AccountNum), '4000');
    LIBSysTableLookup::doLookup(TableNum(CustTable), fieldNums, sortFields, this, returnFieldId, queryRanges);

So the only thing you need to do is specify the fields, returnfields and sortfields…

Ans let’s look at the following example : We need a lookup with a temporary table. Then we can do it like this :
Container   fieldNums       = [FieldNum(TmpIdRef, Name), FieldNum(TmpIdRef, HelpTxt)];
    Container   sortFields      = [FieldNum(TmpIdRef, Name)];
    FieldId     returnFieldId   = ConPeek(fieldNums, 1);
    TmpIdRef    tmpTable;
    tmpTable = LIBDifferenceAction::BuildActionClassList();
    XYZSysTableLookup::doLookup(TableNum(TmpIdRef), fieldNums, sortFields, this, returnFieldId, true, tmpTable);

refresh, reread, research, executeQuery - which one to use?

X++ developers seem to be having a lot of trouble with these 4 datasource methods, no matter how senior they are in AX.

1. Common mistakes
Often, developers call 2 of the mentioned methods in the following order:







formDataSource.refresh() / formDataSource.reread()

All of these are wrong, or at least partially redundant.
Hopefully, after reading the full post, there will be no questions as to why they are wrong. Leave a comment to this post if one of them is still unclear, and I will try to explain in more detail.

2. Refresh
This method basically refreshes the data displayed in the form controls with whatever is stored in the form cache for that particular datasource record. Calling refresh() method will NOT reread the record from the database. So if changes happened to the record in another process, these will not be shown after executing refresh().

Does a redraw of the grid rows, depending on the optional argment for specifying the number of the record to refresh (and this means the actual row number in the grid, which is less useful for AX devs). Special argument values include -1, which means that all records will be redrawn, and -2, which redraws all marked records and records with displayOptions. Default argument value is -2.
This method should be used sparingly, in cases where multiple rows from the grid are updated, resulting in changes in their displayOptions, as an example. So you should avoid using it as a replacement for refresh(), since they actually have completely different implementations in the kernel.
Also, note, that refreshEx() only redraws the grid, so the controls not in the grid might still contain outdated values. Refresh() updates everything, since this is its intention.

3. Reread
Calling reread() will query the database and re-read the current record contents into the datasource form cache. This will not display the changes on the form until a redraw of the grid contents happens (for example, when you navigate away from the row or re-open the form).
You should not use it to refresh the form data if you have through code added or removed records. For this, you would use a different method described below.

How are these 2 methods commonly used?
Usually, when you change some values in the current record through some code (for example, when the user clicks on a button), and update the database by calling update method on the table buffer, you would want to show the user the changes that happened.
In this case, you would call reread() method to update the datasource form cache with the values from the database (this will not update the screen), and then call refresh() to actually redraw the grid and show the changes to the user.

Clicking buttons with SaveRecord == Yes
Each button has a property SaveRecord, which is by default set to Yes. Whenever you click a button, the changes you have done in the current record are saved to the database. So calling reread will not restore the original record values, as some expect. If that is the user expectation, you as a developer should set the property to No.

4. Research
Calling research() will rerun the existing form query against the database, therefore updating the list with new/removed records as well as updating all existing rows. This will honor any existing filters and sorting on the form, that were set by the user.

The research method starting with AX 2009 accepts an optional boolean argument _retainPosition. If you call research(true), the cursor position in the grid will be preserved after the data has been refreshed. This is an extremely useful addition, which solves most of the problems with cursor positioning (findRecord method is the alternative, but this method is very slow).

5. ExecuteQuery
Calling executeQuery() will also rerun the query and update/add/delete the rows in the grid. The difference in behavior from research is described below.
ExecuteQuery should be used if you have modified the query in your code and need to refresh the form to display the data based on the updated query.

formDataSource.queryRun().query() vs formDataSource.query()
An important thing to mention here is that the form has 2 instances of the query object - one is the original datasource query (stored in formDataSource.query()), and the other is the currently used query with any user filters applied (stored in formDataSource.queryRun().query()).
When the research method is called, a new instance of the queryRun is created, using the formDataSource.queryRun().query() as the basis. Therefore, if the user has set up some filters on the displayed data, those will be preserved.
This is useful, for example, when multiple users work with a certain form, each user has his own filters set up for displaying only relevant data, and rows get inserted into the underlying table externally (for example, through AIF).
Calling executeQuery, on the other hand, will use the original query as the basis, therefore removing any user filters.
This is a distinction that everyone should understand when using research/executeQuery methods in order to prevent possible collisions with the user filters when updating the query.

Friday, May 21, 2010

Getting started with tracing

Install the trace parser

Set up the server to capture tracing

1. Go to your server configuration in administrative tools.

2. Manage > Create configuration, give it a name, ok

3. Tracing tab:

Check X++ method calls

Check Function calls, Check the 'Sql Statement', 'Bind Variables'

Set number of nested calls to 99

Check Allow client tracing on AOS instance

4. Hit Apply

5. Hit OK

6. Go to your Client configuration in administrative tools.

7. Manage > Create configuration, give it a name, ok

8. Tracing tab:

Check X++ method calls

Check Function calls

Set number of nested calls to 99

9. Hit Apply

10. Hit OK.

11. Restart your AOS.

12. Re-launch your Client configuration screen.

Capture a trace

1. Launch your client as 'Administrator' by righ clicking and perform the action you want to trace once without tracing on (to warm up the caches)

2. In the client config click on "Start tracing"

3. Walk the action you want to trace again.

4. In the Client config click on "Stop tracing".

5. Go to the directory specified at the top of the tracing tab in the server config and sort by modified date. The most recent file should be a .trc file.

Analyze the trace

1. Start the trace parser. If it’s the first time you run it, you’ll be asked a bunch of questions. Be agreeable.

2. File > Import, pick that .trc files you just captured.

3. Start analyzing

I normally first go to the X++/RPC tab and filter by type, totals. Sort by Exclusive RPC calls first, see what’s causing issues. Then sort by inclusive RPC calls and see what’s causing issues. Then Exclusive duration, inclusive duration, etc. General rule of thumb is if you see “your code” in the top couple pages of data for any of those things, you should work to make it better. You can jump to the call stacks causing the calls and stuff. Just look for random ways to make your stuff faster, pull from caches instead of doing round trips, cut down on how many times you’re called, etc. No real science to it, just look for crappy stuff and fix it.

If you want to trace the client it’s the exact same thing, only use the client config instead.

If it doesn’t work

· Make sure you have at least 20 gig of free space on the drive where your log files are located. ETW doesn’t like to run with less than that.

· Make sure you restarted the client or AOS after setting up the tracing options.

· Make sure you wait about 15 seconds after you click “start trace” before you start doing stuff. It takes a couple seconds (usually about 5) before the tracing turns on.

Saturday, January 23, 2010