Category Archives: Ax 2009

SystemSequences Update


If you ever get a error where the RecId being inserted into the table already exists then the SystemSequences table is to be blamed for this

In some of my previous blog posts, i update the system sequence table’s NextVal record, which determines the RecId to be given. However, this is tricky.

  1. Update in SQL
    • Stop the AOS
    • Update the NextVal recod in the SystemSequences Table
    • Start the AOS
  2. Write a job
static void SetNextRecId(Args _args)
{
    SystemSequences seq;
    ttsBegin;
    select firstonly forupdate crosscompany seq
        where seq.tabId == 123456; // use the table id here or tablenum()
    seq.skipTTSCheck(true);
    seq.skipDatabaseLog(true);
    seq.selectForUpdate(true);
    seq.nextVal = 5637123456 + 1; // enter the last recId for the table
    seq.update();
    ttsCommit;
}

You may need to run this job and restart the AX client only if the just the above doesn’t work

static void sab_recIdSequenceFix(Args _args)
{
    SystemSequence systemSequence = new systemSequence();
    Tableid tableId = 123456; // use the table id or tablenum() here

    systemSequence.suspendRecIds(tableId);
    systemSequence.suspendTransIds(tableId);
    systemSequence.flushValues(tableId);
    systemSequence.removeRecIdSuspension(tableId);
    systemSequence.removeTransIdSuspension(tableId);
}

X++ date addition issue


I came across this issue with adding dates to negative integers. Basically the issue is that you cant add a negative value to a date variable (but you can subtract a positive integer).
i.e. if x = -5, and date1 is 01/01/2012,
and you do date1 = date1 + x,
this results in a runtime error which says “Error executing code”. This error is not even caught by the catch block.

Following is a job i wrote to prove this.


static void DateOperatorIssue(Args _args)
{
    Date date1,date2,date3;
    int dateDiff;
    ;
    try
    {
        date1 = mkdate(01,01,2013);
        date2 = mkdate(10,01,2013);
        date3 = mkDate(15,01,2013);
        dateDiff = date3 - date2;
        info(strfmt("Date diff is: %1", dateDiff)); //dateDiff = 5
        date1 += dateDiff;                          //This works (You can only add positive integers, or subtract positive integers)
        info(strfmt("New date is: %1", date1));
        dateDiff = date2 - date3;
        info(strfmt("Date diff is: %1", dateDiff)); //dateDiff = -5
        date1 += dateDiff;                          //Runtime error: Error executing code: Wrong type of argument for conversion function.
        info(strfmt("New date is: %1", date1));
    }
    catch(Exception::Error)
    {
        error('ooooops');                           //This is never reached
}
}

Dynamics Ax Debug logging (File based)


The need for logging in Ax for me came from the .Net world of using Debug.Writeline , and then looking up the debug values from sysInternals dbgview.
However, to make a very quick and just basic configurable logging, i went to write the logs into a file.
This is a quick and dirty way to add logging capabilities into Dynamics ax.

The project can be found at Codeplex: http://axug.codeplex.com/SourceControl/changeset/view/82084#1932052
To go on with the current logging, following is the code signature of the method to be called for passing the logging message

public static void LogMessage(str _processId, str _text = '', int _step = 0)

and logging can be called by

Logging::LogMessage("Testing Logging","Logging is great :)", 0);

The Macro values define if the logging capability is turned on, and also the filename (So it can be configured from the usr layer / production site)

I have tested this with Ax 2012 and it has worked pretty well so far, and i do not think Ax2009 should have an issue with it. This logging is so far restricted to file based logging, but I am looking at incorporating other forms like Debug.WriteLine, and will further look into incorporating log4Net (however i have a strong suspicion that this will be limited to server side, and only for Ax 2012).

Hope this brings enough joy to you out there.
Happy Logging

NOTE: before you run the test class in the project, please make sure to edit the Macro to specify the folder path (This can be set to a local directory, but please be advised, this method is “called from”)

UPDATE:

I have now updated the code to include 2 more logging features. Along with that made changes to the method to include the type of message being passes (i.e. warning, information or error. We shall see why)

Change set: http://axug.codeplex.com/SourceControl/changeset/82210

1. Windows Event logging. – This enables you to you write into the Windows Event log directly. The log type (info , error, warning) will correlate directly to the log icons in the event viewer 🙂

2. Debug log: The most simplistic logging with .Net’s System.Diagnostics.Debug.WriteLine is now included. Although i have seen that this also writes to the event log 😦 (as information only, probably something that is handled from x++ itself, so may not be a good idea to have it in a production server where it manages to clutter the event viewer)

Iterate through AOT tree


I came across this piece of code which was required to check the AOT elements for certain things.
This iteration should work fine as long as all the developments done show up in your session.
The best way is to restart the AOS and run it to make sure the utilElements have all new objects

UtilElements e;
UtilEntryLevel utilLevel = global::currentAOLayer();
TreeNode treeNode;
;
while select e
where e.utilLevel == utilLevel
&& !(e.recordType == 37 //SharedProjects
|| e.recordType == 78) //WebListDef
{
//treeNode = xUtilElements::getNodeInTree(e);
treeNode = xUtilElements::getNodeInTree(xUtilElements::parentElement(e));
//DO SOMETHING WITH THE treeNode HERE.
}

Ax bug in X++ join statement – Compile error


So i have found a bug in Ax relating to join statement.
Based on the Image, you can see that i am joining 2 tables, and setting a where statement for the first one to a tableId
All 3 should give me the same result.
However i get a compile error in the 3rd statement. It involves around checking the RefTableId field of Table 2, and comparing it to a tablenum method.

Ax X++ select statement join bug
Ax X++ select statement join bug

The 1st method uses the same functions, but all the where clauses are after the join statements. This however can get ugly (unreadable) when there are a lot of where clauses as you would like to have them grouped up.
//Join scenario 1
select firstonly table2
join table1
where table2.MyRefTableId == tablenum(Table2)
&& table1.SomeId == "ID00001";

The 2nd method of select statements groups the where clauses nicely. However to do that, I have to use the tablename2Id function instead.
//Join scenario 2
select firstonly table2
where table2.MyRefTableId == global::tableName2Id(tablestr(Table2))
join table1
where table1.SomeId == "ID00001";


The 3rd method is the culprit, for which i have no explanation at the moment. Anyone want to shed some light on this?
//Join scenario 3
select firstonly table2
where table2.MyRefTableId == tablenum(Table2)
join table1
where table1.SomeId == "ID00001";

This gives me a “Syntax error”

Validate table record fields through X++


This is a code snippet I use to validate a record created in X++. I do not have to manually go and check if the group I am adding exists in the reference table. The validateField method does this, but is only called from the UI. This topic has been added by me previously here

The following code goes through each line and validates the field, and then the record itself. Any error messages are populated in the infolog (just like the UI)
It could be placed as a static method and can be used application wide with one line of code

public static boolean validateRecord(common _common)
{
    boolean ret = true;
    DictTable dictTable = new DictTable(_common.TableId);
    int fields;
    int i;
    ;

    fields = dictTable.fieldCnt();
    //iterate through each field and validate it
    for(i = 1; i    {
        ret = ret && _common.validateField(dictTable.fieldCnt2Id(i));
    }

    //validate the record itself over here
    ret = ret && _common.validateWrite();

    return ret;
}

To implement this code we can call it like this

inventTable.itemId = "NewItem0001";
inventTable.itemGroupId = "NewItemGroup001";
/*
..
Other data
*/
//Check if the item validates
if(Validations::validateRecord(inventTable))
{
    inventTable.insert();
}

For the above example, an error will be shown because the itemGroupId does not exist.

Hopefully this help others 🙂

Backup and restore MorphX Version control


When an Ax environment is setup for version control with MorphX, there are times when the database needs to be restored, or another database is required to be used instead. This causes the loss of all version control data. Ax has some standard tables where it stores all this data which can be backed up and restored.
The tables used by Ax for MorphX version control are:

  • SYSVERSIONCONTROLMORPHXITE2541
  • SYSVERSIONCONTROLMORPHXLOC2542
  • SYSVERSIONCONTROLMORPHXREV2543
  • SYSVERSIONCONTROLPARAMETERS
  • SYSVERSIONCONTROLSYNCHRONI1982

However due to certain Ax frameworks its just not a straight copy.

The Backup procedure:

In this procedure, i create an SSIS task and add it to a Job that runs nightly backing up data from the tables specified above to a seperate database, which is then completely backed up. This is a simple “export data” functionality within SQL, and saved in the SSIS and attached to a job.

The restore procedure:

This is where a similar export data from the backup database is done to the Ax database. There are a couple of things that need to be done before importing the data. Assuming that a different database has been restored, and Ax is compiled and syncronized.

  1. Ax does not create the tables defined above on syncronization. The parameters for MorphX need to be turned on.
  2. After setting up MorphX version control, go to Tools –> Development tools –> Version Control –> Setup –> System Settings –> click Ok.
    This creates the version control XML file definition and checks it out (Resources\SysVersionControlSystemMorphXDefFile). Check this file in and we are ready to
  3. Shut down the AOS to avoid any conflicts
  4. Import the data from the 5 tables defined above into the Ax database
  5. Now this is where we need to reset the record Id’s of Ax. To do so open SQL and run the following commands on the Ax database
    Update systemSequences Set nextval = (select max(RecId) + 1 from SYSVERSIONCONTROLMORPHXREV2543) Where tabid = 2543;
    Update systemSequences Set nextval = (select max(RecId) + 1 from SYSVERSIONCONTROLMORPHXITE2541) Where tabid = 2541;  
  6. After starting the AOS the version control should be restored

Update 26/10/2015: For 2012 application the table names are used in full and not terminated by the string limit

Show query values on form


To show query values just like a report dialog does there are some internal classes that need to be used.

To simplify this process, I have created a class, which will take in the form group control and the query and use that to generate fields just like a report dialog would do.

The code can be downloaded from codeplex/axug

To show an example of how this looks, here is a form, with an empty group, and a button to select the query values

image

The button “Select values” is calling queryRun.Prompt for the queryrun variable defined within the form object.

 

 

 

 

image

Once the user selects the fields (user can even leave certain ranges empty, and add more range fields) the query is passed to a class along with the forms group control (i.e. “My Query Group”)

The class will then clear existing controls within the group and then add the ranges from the query.

image

Multiple data sources can be added by the user and they all show up in the group.

A shortcoming of this is that every time the class arranges the fields in the group, it first hides any existing controls. This is because, controls cannot be removed when added. So a lot of selects can cause a large memory footprint for this form

Validate Table from X++


Dynamics Ax forms calls the validateField whenever the field value changes. However, this is only called from the Forms. To validate a table for its values from X++, the following code does it

static server boolean validateTable(common _common)
{
boolean ret = true;
SysDictTable    dictTable;
int fldCnt, i;
fieldId fid;
;

dictTable = new SysDictTable(_common.TableId);
fldcnt = dictTable.fieldCnt();
for(i = 1; i <= fldcnt; i++)
{
fid = dictTable.fieldCnt2Id(i);
ret = ret & _common.validateField(fid);
}

return ret;
}

Show batch class in task list


Sometimes running a batch job may depend on another one completing before it runs. To ensure that can happen, and to set it up involves some development work.

To add a batch job to an existing batch task list, lets first set up a batch job

imageFor this example i have created a batch job, and added the class InventTransferMultiPick (Transfer order – Picking list) to the list.

I have created my own batch job called MyBatchClass. This class needs to run only after the InventTransferMultiPick class has finished executing.

When I try to insert another record into the Batch tasks, it does not show this class there. For reasons, there is a setting required in the class called canGoBatchJournal. This method by default returns false, and needs to be explicitly overridden and set to true.

protected boolean canGoBatchJournal()
{
    /*
    boolean ret;
    ret = super();
    return ret;*/
    return true;
}

This then allows the batch to be added as a task manually:

image

You can set the parameters:

image

And set a condition in the “Has condition” group:

image