Friday, November 30, 2012

Value cannot be null. Parameter name: x

Don't get confused, when trying to find where this error message was thrown: "Value cannot be null. Parameter name: x". Especially, when this is something thrown during AIF processing.

You might suggest that this is a label and you do find the label @SYS91439, but still you have no idea what is going on, e.g. no breakpoints with this label are hit.

So, it may well be something thrown by an arithmetic operation, which was passed a null value.


Another good reason to give meaningful names to variables and check for null values.

Sunday, November 4, 2012

Computed columns in Views by example

There is a bug in the standard AX, in the query named AxdPurchaseRequisition. The problem is here:


PurchLineAllVersions datasource is based on a View object, so PurchLineAllVersions.TableId contains table ID of the view itself, and not that of the underlying tables. As a result, no DocuRefTrans records will be found with this quiry.

The issue may be fixed by using computed columns in views - the new feature in AX 2012.

As you may already know, the PurchLineAllVersions view is based on the quiry PurchLineAllVersions, which, in turn, is a union of 2 other views: PurchLineArchivedVersions and PurchLineNotArchivedVersions.

First, add a method to the 2 views.

PurchLineNotArchivedVersions:

public static server str purchLineTableId()
{
    return SysComputedColumn::returnLiteral(tableNum(PurchLine));
} 

PurchLineArchivedVersions:

public static server str purchLineTableId()
{
    return SysComputedColumn::returnLiteral(tableNum(PurchLineHistory));
}

Add a column to the 2 views: right-click on the Fields, then "New -> Int Computed Column". Set the new fields' properties like that:


Then, add new PurchLineTableId fields to the PurchLineAllVersions query datasources and the PurchLineAllVersions view.

Finally, change the faulty link in the AxdPurchaseRequisition quiry, so that it will connect PurchLineAllVersions.PurchLineTableId and DocuRefTrans.RefTableId.

By the way, there is another, better example of how computed columns may be used: take a look at the InventValue* views, which are used by the Inventory Value report.

Thursday, November 1, 2012

xRecord.setTmp() and inactive configuration keys

Be carefull when using setTmp method on table buffers. If your table is assigned a configuration key, turning it off will make this line of code throw an error at runtime: "Table '<table name>' of type TempDB cannot be changed to table of type InMemory".

For example, \Classes\SysRecordTemplateEdit\setFormDataSourceRecord has this problem. It loops through form datasources and tries to set them all to InMemory withouth checking first if they are TempDB or not.

In order to avoid this error, you can use the following construction:

    if (!myTable.isTempDb())
    {
        myTable.setTmp();
    }

More about TempDB tables on MSDN.

Exporting to Excel with Microsoft.Dynamics.AX.Fim.Spreadsheets.* classes

While looking for a way to export to Excel in batch, I investigated what they do in financial statements (LedgerBalanceSheetDimPrintExcelEngine class).

Pros:
  • It is possible to use these .NET components in batch on the server side
  • Execution is way faster than that of the SysExcel* classes

Cons:
  • It looks like it is not possible to create more columns than there are letters in the English alphabet
  • Less flexible comparing to standard SysExcel classes, so manual formatting will likely be needed in the end. 
static void AnotherWayToExportToExcel(Args _args)
{
    #define.ReadWritePermission('RW')
    #define.FileName('c:\myFile.xlsx')
    #define.ExcelColumnWidth(15)
    #define.ExcelCellFontSize("Microsoft.Dynamics.AX.Fim.Spreadsheets.CellFontSize")
    #define.Size9("Size9")    
 
    CustTable custTable;
 
    Microsoft.Dynamics.AX.Fim.Spreadsheets.Spreadsheet spreadsheet;
    Microsoft.Dynamics.AX.Fim.Spreadsheets.CellProperties cellProperties;    
    Microsoft.Dynamics.AX.Fim.Spreadsheets.ColumnProperties columnProperties;    
 
    void addColumn(str _name)
    {
        columnProperties = new Microsoft.Dynamics.AX.Fim.Spreadsheets.ColumnProperties();
        columnProperties.set_Width(#ExcelColumnWidth);
        spreadSheet.InstantiateColumn(columnProperties);
 
        cellProperties = new Microsoft.Dynamics.AX.Fim.Spreadsheets.CellProperties();
        cellProperties.set_FontSize(CLRInterop::parseClrEnum(#ExcelCellFontSize, #Size9));
        cellProperties.set_Bold(true);
 
        spreadSheet.AddStringCellToWorkbook(_name, cellProperties);
    }    
 
    new FileIOPermission(#FileName, #ReadWritePermission).assert();
 
    spreadSheet = new Microsoft.Dynamics.AX.Fim.Spreadsheets.Spreadsheet();
 
    if (!spreadSheet.CreateSpreadsheet(#FileName))
    {
        throw error(strFmt("@SYS72245", #FileName));
    }
 
    addColumn("Customer name");
    addColumn("Balance");    
 
    while select custTable
    {
        spreadSheet.MoveToNextRowInWorkbook();        
 
        cellProperties = new Microsoft.Dynamics.AX.Fim.Spreadsheets.CellProperties();
        cellProperties.set_FontSize(CLRInterop::parseClrEnum(#ExcelCellFontSize, #Size9));        
 
        spreadSheet.AddStringCellToWorkbook(custTable.name(), cellProperties);            
        spreadSheet.AddNumberCellToWorkbook(real2double(custTable.openBalanceCur()), cellProperties);                    
    }
 
    spreadSheet.WriteFile();
    spreadSheet.Dispose();
 
    CodeAccessPermission::revertAssert();    
}

Output: