Fred's profileFred ShenBlogLists Tools Help

Fred Shen

Occupation
A million thanks to Powerise, all the team members of Axapta Chinese localization project team, Karen, William and Baz.
Lists

Fred Shen

Dynamics AX Development
April 09

X++ code to remove identical copy

Our client asked for a job to remove the identical copy from VAR layer.
For some unknown reason, some AOT objects are touched in VAR layer but actually are identical copy. When the developer compared the VAR layer object with the one in lower layer (BUS, SYS etc.), AX showed it was an identical copy.
 
Here is the example on how you can remove the identical copy in X++ code:
static void FindAndDeleteIdenticalObjects(Args _args)
{
    SysTreeNode     comparable1, comparable2;
    TreeNode          curLevelTreeNode, upperLevelTreeNode;
    UtilIdElements    utilElements, joinUtilElements;
    ;

    while select UtilElements
        where UtilElements.utilLevel        == UtilEntryLevel::var &&
              (
                UtilElements.recordType     == UtilElementType::Form         ||
                Utilelements.recordType     == UtilElementType::Report      ||
                Utilelements.recordType     == UtilElementType::Table        ||
                Utilelements.recordType     == UtilElementType::Class         ||
                Utilelements.recordType     == UtilElementType::Enum        ||
                Utilelements.recordType     == UtilElementType::ExtendedType
              )
    {
        //Should use join if for a normal table, but not applicable for UtilElements
        //Performance hit if use exists join
        select firstonly recid from joinUtilElements
            where joinUtilElements.utilLevel     !=  UtilElements.utilLevel    &&
                  joinUtilElements.name            == UtilElements.name        &&
                  joinUtilElements.recordType   == UtilElements.recordType;
        if (joinUtilElements.RecId)
        {
            //Thanks for Jim Shepherd here
            curLevelTreeNode      = SysTreeNode::findNodeInLayer(UtilElements.recordType, UtilElements.name, UtilElements.parentId, UtilElements.utilLevel);
           
            upperLevelTreeNode  = SysTreeNode::getLayeredNode(curLevelTreenode, 1);
            comparable1              = SysTreeNode::newTreeNode(curLevelTreeNode);
            comparable2              = SysTreeNode::newTreeNode(upperLevelTreeNode);
            if (SysCompare::silentCompare(comparable1, comparable2))
            {
                info(strFmt("Element name: %1, Element type: %2", UtilElements.name, enum2str(UtilElements.recordType)));
                //Remove the node
                curLevelTreeNode.AOTdelete();
            }
       }
   }
}
September 04

New server-based batch framework in AX 2009

Microsoft Dynamics AX 2009 introduces a new batch framework that supports server-based batches without the need for a client. All new batch jobs in Microsoft Dynamics AX 2009 use the new batch framework. Most existing batch jobs from previous releases are being migrated to the new framework. Client-side batches are supported in Microsoft Dynamics AX 2009 but are not recommended.

From AX 2009, we don't need to run a client to run scheduled jobs, if you specify that the batch job runs on server. The batch job runs on server by default unless the developer overrides the runImpersonated method and make it return false.

The old client-based batch processing is still in AX 2009 for backward compatibility and will be removed in future versions. The old client-based batch system, that was based on a dedicated client, cannot be replaced in some situations. For example, due to some technical problems that the compiler cannot be run on the server tier, which means the task to run a scheduled compilation or cross-reference update must be run on client-tier.
August 26

Add a Dynamics AX user to be a memebr of Sharepoint site group

In AX 4.0, it is allowed to link an AX user to a particular Sharepoint site group from main menu -> Administration -> Users -> User relations button -> Web sites tab. Unfortunately, this is removed from AX 2009.
However, we can still add this feature back if we like by using X++.
 
The example below show how easy can add an AX user to Sharepoint site owner groups by using X++ and CLRInterop.
Before you run this job, please add Microsoft.Sharepoint.dll (WSS 3.0) as a reference on AOT if it is not registered in GAC.
You can find this dll in C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\ISAPI on the server that Enterprise Portal resides.
 
static void AddAXUserToSharepointGroup(Args _args)
{
    Microsoft.SharePoint.SPWeb                   spWeb;
    Microsoft.SharePoint.SPSite                    spSite;
    Microsoft.SharePoint.SPGroupCollection  spGroupCollection;
    Microsoft.SharePoint.SPGroup                spGroup;
    Microsoft.SharePoint.SPUserCollection    spUserCollection;
    Microsoft.SharePoint.SPUser                  spUser;
    EPWebSiteParameters                           epWebSiteParameters;
    UserInfo                                              userInfo;
    SysUserInfo                                         sysUserInfo;
    str                                                       loginName;
    System.Exception                                 ex;
    str                                                       siteGroupName;
    ;
   
    try
    {
        //Sharepoint site registered in AX2009
        select firstonly epWebSiteParameters;
        select firstonly userInfo
            where userInfo.id == curUserId();
        select firstonly sysUserInfo
            where sysUserInfo.Id == curUserId();
        loginName           = userInfo.networkDomain + @"\" + userInfo.networkAlias;
        spSite              = new Microsoft.SharePoint.SPSite(epWebSiteParameters.InternalUrl);
        spWeb               = spSite.get_RootWeb();
        //Create a user in Sharepoint
        spUserCollection    = spWeb.get_SiteUsers();
        spUserCollection.Add(loginName, sysUserInfo.Email, userInfo.name, "");
        //Add the user to a site owner group
        spUser              = spUserCollection.get_Item(loginName);
        spGroupCollection   = spWeb.get_SiteGroups();
        siteGroupName       = CLRInterop::getAnyTypeForObject(spWeb.get_Title()) + " Owners"; //Or Visitors, Members
        spGroup             = spGroupCollection.get_Item(siteGroupName);
        spGroup.AddUser(spUser);
        spGroup.Update();
    }
    catch (exception::CLRError)
    {
        ex = CLRinterop::getLastException();   
        if (ex)
        {
            info(ex.ToString());          
        }
    }
    catch (exception::Internal)           
    {
        ex = clrinterop::getLastException();       
        if (ex)
        {
            info(ex.ToString());           
        }
    }
}
 
December 08

Failed to create COM objects on AX 4 AOS

You may come across this issue when attempt to initialize a COM object on AX 4 AOS server side:
 
static server void ActivateCOMOnAOS()
{
    #define.Word('Word.Application')
    COM com;
    InteropPermission permission = new InteropPermission(InteropKind::ComInterop);
    ;
 
    permission.assert();
    //BP deviation documented
    com = new COM(#Word);
    CodeAccessPermission::revertAssert();
}
And an error message will appear:
COM object of class 'Word.Application' could not be created. Ensure that the object has been properly registered on computer 'ComputerName'.
 
It is because by default, Microsoft Word as a COM object can not be launched by the user NT AUTHORITY\NETWORK SERVICE. 
 
The way to resolve this issue is to modify the Activation permission for Word Application using the Component Services administrative tool.
 
Configure DCOM (Windows 2003)
  • Start -> All programs -> Administrative Tools -> Component Services.
  • Expand Component Services
  • Expand Computers
  • Expand My Computer
  • Select the DCOM Config item
  • Select the Microsoft Word Document item.
  • Right click and select Properties
  • Under Launch and Activation Permissions select the Customize option.
  • Click the Edit button
  • Click the Add button to add a new account to the list.
  • Click the OK button
  • On the dialog that is displayed enter Network Service as the account name.
  • Grant Network Service the launch and activate permission.
August 16

Consuming a Web Service in Dynamics AX 4

This will tell you how to use Common Language Runtime Interoperability feature in Dynamics AX 4 to reference a web service.  There is a pretty good discussion about this on Microsoft MBS community by Dilip and Mike Frank.

To continue, at least you need to have Dot Net Framework SDK 2.0.  (Not Visual Studio .Net) We will use the language translation service on www.stanski.com as the referenced web service.

Procedure:
Download and Install .NET Framework Version 2.0 Software Development Kit.

Open a Microsoft .NET Framework command prompt. Click Start, point to All Programs, point to Microsoft .NET Framework SDK 2.0, and then click SDK Command Prompt.

At the command prompt, run the following command:
Wsdl /l:cs /protocol:soap
http://www.stanski.com/services/translate/translate.asmx
This will generate a C# source code file of proxy classe, which in this situation is TranslationService.cs

Then we need to edit TranslationService.cs file to add namespace for it.
Use NotePad to edit TranslationService.cs, the edited codes should look like
//------------------------------------------------------------------------------
// <auto-generated>

namespace DemoAXWebService //Add namespace declaration
{
    using System;
    …
    …
}//End namespace declaration

Compile the cs file to dll file. Type this in the command prompt
csc /t:library TranslationService.cs

This will generate the TranslationService.dll.
Copy the TranslationService.dll to \Program Files\Microsoft Dynamics AX\40\client\Bin.

Start Microsoft Dynamics AX 4.0
Open Application Object Tree, and locate the Reference node. Right click, select Add Reference and register the TranslationService.dll in Dynamics AX.

Create a new job named TestWebService
static void TestWebService(Args _args)
{
    DemoAXWebService.TranslationService translateService
     = new DemoAXWebService.TranslationService();
    str strLanguage, strMessage;
    str translateResult;
    ;

    strLanguage = 'en_de';
    strMessage  = 'Test Web Service';
    translateResult = translateService.Translate(strLanguage, strMessage);
    Info(translateResult);
}

Run the job, you will get the result “Test-Netz-Service”.

 

July 20

RefRecID in Dynamics AX 4

Dynamics AX 4.0 will generate unique RecIds per table, whereas Axapta 3.0 generates unique RecIds per company account. 
 
This may have a great impact on the use of EDT RefRecId in AX development. In Axapta 3.0, we can directly use refRecId for table fields, because recId is unique per company account. Well, in AX 4, it is not the case anymore, how can you reference the recId within the same company account? For example, recId 5637144576 can exist in Address table, AddressCountryRegion table and AddressCounty table.

As a result, in AX 4, do not use the RefRecID data types directly for table fields. The way we use RefRecId data types is create a new EDT derived from EDT RefRecId, and define a relation for it. Then use the new created EDT for table fields.

You may find VendTransOpen table as a sample. The table field RefRecId in VendTransOpen is derived from EDT VendTransRefRecId which extends EDT RefRecId. And a relationship is defined on VendTransRefRecId, which is "VendTransRefRecId == VendTrans.RecId".

Since AX 4.0 will generate unique RecIds per table, and by changing the RecID from a 32-bit to a 64-bit integer, the number of RecIDs available is increased to 2-to-the-64, AX 4.0 does not include SysRecIdRepair utility. 
July 13

'Not Like' in Dynamics AX

In X++, we can use Like '*someIdentifier' to implement the Like keyword.
e.g.
    select firstonly purchTable
        where purchTable.purchId like '00007*';
 
However if you want to use 'Not Like' in X++ SQL statement, you have three options:
The first option, using '!' as 'not',
e.g.
    select firstonly purchTable
         where !(purchTable.purchId like '00007*');
 
The second option, using notExists join
e.g.
    PurchTable purchTable, refPurchTable;
    ;
 
    select firstonly purchTable
        notExists join refPurchTable
        where purchTable.purchId == '00007*';

Please make sure that you do put purchTable.purchId in condition statement, otherwise the SQL statement will retrieve an empty result set.
The last option, using Query
e.g.
    Query query = new Query();
    QueryRun queryRun;
    ; 

    query.addDataSource(tableNum(PurchTable)).addRange(fieldNum(PurchTable, PurchId)).value('!00007*');
    queryRun = new QueryRun(query);
    if(queryRun.next())
    {
        purchTable = queryRun.get(tableNum(PurchTable));
        print purchTable.PurchId;
        pause;
    }
 
Using NotExists join seems more complicated than the first option, but actually there is no performance difference between them.
 
July 04

Cache size for recId allocation

In Dynamics AX, recId is assigned according to the nextVal (ID -1, name SEQNO) of SystemSequence table. But the next value of RecId is not always retrieved from the table, and it is cached in client machine's memory. In AX3.0, the default cache size is 25.
Developers can change the default cache size using SystemSequence class:
In the startupPost method of Application class, you can add the following code:
 
    SystemSequence SystemSequence;  
    …
 
    SystemSequence = new SystemSequence();
    SystemSequence.setCacheSize(30);
 
As a result, recIDs are drawn in sets of 30. That also means that the next value of SystemSequence table will be increased by 30 each time.

In AX 4.0, recIds will be generated unique per table instead of per company.
So for each table, there will be a nextVal (ID -1, name SEQNO) in SystemSequence table. The cache size for recId allocation is increased to 250. And AX 4 does not allow changing cache size for recId allocation anymore. 
July 02

Undefined nodes in System Documentation after applying KR1

I found that if using en-gb or de-at language after applying Kernel Roll Up 1, it will end up with Undefined nodes in System documentation. At the same time, on the property sheet of AOT, you may also find some object property displayed as *undefined* as well. 

What you need to do is edit your ktd file, like axsysen-gb.ktd, and locate line 1393, remove the blank line between Apostrophe(') and 1245. Everything should be back to normal.

That is because ktd file contains the help text for kernel-defined method and property. The blank line makes AX unable to translate the relevant help texts.
June 21

Date data type in Query Range Value Expression

Query range value expressions can be used in any query, where you need to express a range that is more complex than that made possible with the usual dot-dot notation.
Especially to use Or clause in query, you definitely need to use Query Range Value Expression.

Today I just want to investigate more about the date data type in Query Range Value Expression.
To retrieve records where the transaction date is before 21st June, 2006. You can use Date2StrXpp() to format the date:
    queryBuildRange.value(strFmt('(TransDate <  %1)', Date2StrXpp(21062006)));


or to format the date using date literals directly:
    queryBuildRange.value(strFmt('(TransDate <  %1)', @'21\06\2006')));


Besides the expression mentioned above, there is a third way to specify date data type in Query Range Value Expression, even it does not quite make sense.
In Dynamics AX, date data type is represented as 32-bit (4 byte) integer. The second byte, the third byte and the last byte store the information of the day, the month and the year respectively.
So in Query Range Value expression, you can use the following instead to allow AX to transform the date directly,


    Date transDate = 21\06\2006;
    int dayInt, monthInt, yearInt;
    ;
   

    dayInt     = dayofMth(transDate);
    monthInt = mthofyr(transDate);
    yearInt    = year(transDate);
   

    //dateInt should be integer type value rather than real type value
    //otherwise AX can’t figure out!!

    dateInt  = (dayInt-1)*Power(2,16) + (monthInt-1)*Power(2,8) + (yearInt-1900);
   

    queryBuildRange.value(strFmt('(TransDate <  %1)',  dateInt));


Honestly, this is a bit complicated, but just want to show you how AX holds the date value. Actually, I seldom use the third expression in my codes. 


And that is the reason why the scope of date data type is from 1\1\1901 to 31\12\2154.

(In AX 3.0, max date is  31\12\2153, and 31\12\2154 in AX 4.0)

Because AX can only hold dates for up to Power(2, 8), which is 256 years (1900 to 2155). 

June 01

Change the combobox options in RunbaseBatch class

RunbaseBatch class supplies some features, including batch processing, pack/unpack and progress bar etc. On the other hand, we get this request frequently, usually to modify the properties of dialog control in runtime. For example, we add a combobox control on the dialog, but the options of combobox can't be predefined. Well, the combobox can be very conveniently filled with field lists if you are using a form, while the combobox's selection lists in RunbaseBatch is determined by Enum values ( dialog.addField(TypeId(someEnum) ). How can we accomplish this?
First option, change the eunm's values dynamically? Oh, terrible, this means that Axapta needs to add, edit and save AOT node, then synchronize in run time.
Second option, use Form instead of RunbaseBatch class, if you want this Form to be savable and batchable, you need to manually to add some codes on it.
Well, it is no point to do that if there is any other work around. 
There is a third option, and I believe this should do the trick.
Let's start with a simple RunBaseBatch class and a simple scenario, dynamically adding printers' name to the combobox.
Define a BaseEnum called TestRunBaseDialog (ensure that the style property is set to combo box), and create an element for it which is called TestComboboxElement. Then we can set the TestComboboxElement's label as "Select a printer".
Now we can create the RunbaseBatch class from scratch.
 
Declare the class
class TestAddComboOptionsInRunbase extends runbasebatch
{
    int                 printerName;
    DialogField dialogPrinter;
    FormComboboxControl controlCombobox;
    #define.CurrentVersion(1)
    #localmacro.CurrentList
        printerName
    #endmacro
}
 
Pack the value
public container pack()
{
    return [#CurrentVersion, #CurrentList];
}
 
Unpack the value
public boolean unpack(container packedClass)
{
    boolean         _ret;
    Integer         _version    = runbase::getVersion(packedClass);
    switch (_version)
    {
        case #CurrentVersion:
            [_version, #CurrentList] = packedClass;
            break;
        default:
            _ret = false;
    }
    return _ret;
}
 
Design the dialog, we need to activate the dialogSelectCtrl method. And please notice that, there should be some other dialog control to be added before dialogPrinter. The reason is that after the Dialog form has been built, the selected control can not be the dialogPrinter, otherwise, we can't implement the dialogSelectCtrl method when we look up the options in dialogPrinter combobox. So we add a Text before adding combobox.
protected Object dialog(DialogRunbase _dialog, boolean _forceOnClient)
{
    DialogRunBase dialog;
    ;
    controlCombobox = new FormComboboxControl();
    dialog = super(_dialog, _forceOnClient);
    dialog.caption("Dialog for printer option");
    dialog.addText("Choose Printer");
    dialogPrinter = dialog.addField(typeId(TestRunBaseDialog));
  
    dialog.allowUpdateOnSelectCtrl(true);
    return dialog;
}
 
Okey, we can define how to change the selection list in the combobox now.
public void dialogSelectCtrl()
{
    controlCombobox = dialogPrinter.fieldControl();
    controlCombobox.clear();
    controlCombobox.add("Printer One");
    controlCombobox.add("Printer Two");
    controlCombobox.add("Printer three");
    //… more printers here
}
 
Process your business logic in run()
void run()
{
    ;
    switch (printerName)
    {
        case 0:
        info ("You are choosing printer one.");
        break;
        case 1:
        info ("You are choosing printer two.");
        break;
        case 2:
        info ("You are choosing printer three.");
        break;
    }
    //Put your logic here
}

Finally, put the codes to Main()
static void main(Args _args)
{
    TestAddComboOptionsInRunbase testRunBase = new TestAddComboOptionsInRunbase ();
    ;
    if (testRunBase.prompt())
    {
        testRunBase.run();
    }
}
 
The reason why we use dialogSelectCtrl() is that the dialog control in RunbaseBatch class is not built before the prompt method, the dialogPrinter is still the object of FormBuildControl. Only FormComboboxControl object can add and change the selection list. After Axapta implement the runbaseBatch.prompt(), dialogPrinter has become the object of FormComboboxControl. So in the dialogSelectCtrl(), we can modify the selection list of dialogPrinter combobox.
May 26

Limited sessions in Axapta

Some Axapta system administrator may feel it is really annoying that Axapta allows the user to create multiple Axapta sessions and log in as the same user. If you want to give the user notification prior to logging in as the same User ID, here comes the solution.
Under the Info class, modified the startupPost method like the codes below:
void startupPost()
{
    int         counter;
    int         num = 0;
    int         maxSessions = Info::licensedUsersTotal();
    xSession    session;
    UserInfo    userInfo;
    UserId      currentUserId;
    ;

    currentUserId = curuserid();
    for(counter = 1; counter < maxSessions;counter++ )
    {
        session = new xSession(counter, true);
        if(session && session.userId())
        {
            select firstOnly userInfo
                where userInfo.id == session.userId();

            if (userInfo && (currentUserId == session.userId()))
            {  
                num++ ;
            }
        }
    }

    if (num > 1)
    {
        if(box::yesno("The same user id can't log in twice. Do you want to log in anyway? ",
                   DialogButton::Yes, "Log in", "Log out") == DialogButton::No)
        {
            infolog.shutDown(true);
        }
    }
}

Please notice that, if you modify the startupPost method of Application class, that works only in 2-tier environment. For both 2-tier and 3-tier environment, it is recommended to modify the startupPost method of Info class.

May 17

Create a new method in runtime

Some one may need to use codes to create or edit a method in Axapta.
Here is an example to show how to create a lookup method for a form's field in runtime.

    static void CreateFieldMethod(Args _args)
    {

        TreeNode tn1,tnAddr, methodsNode;
        MemberFunction memberFunction;
        str source;
        ;
 
        //Thanks for Max Belugin's comments,
        //it is really good to use Verbatim String as well.
        //The reason why I use escape characters here is
        //because this line of code is copied from standard Axapta application :)
        tn1 = infolog.findNode(
"\\Forms\\Address\\Data Sources\\Address\\Fields\\AddrRecId");

        tnAddr = infolog.findNode( "
\\Forms\\Address" );
        methodsNode = tn1.AOTfindChild( 'Methods' );
        methodsNode.AOTadd('lookup');
        memberFunction = methodsNode.AOTfindChild( 'lookup' );
        source =
@"public void lookup(FormControl _formControl, str _filterStr)
                         {
                                super(_formControl, _filterStr);
                         }" ;
        memberFunction.AOTsetSource(source, false);
        memberFunction.AOTsave();
        methodsNode.AOTsave();
        tnAddr.AOTcompile();
    }
March 23

Convert Axapta date type value to datetime type value in SQL Server

It is known that, in Axapta, date data type only contains values of day, month and year whereas in SQL Server, datetime data type contains values of second, minutes and hour as well.  So if you want to retrieve values from SQL Server using ODBCConnection, and you need to specify the condition on some date type comlumn, how can you get it done?
Here comes the code to show you how to convert Axapta’s date data type value to datetime value. It is an example to get a voucher value from LedgerTrans table in a given date.
static void TestDateTimeConversion(Args _args)
{
    LoginProperty             loginProperty = new LoginProperty();
    ODBCConnection      con;
    Statement                    stmt;
    str                                  sqlString, result;
    ResultSet                     resultSet;
    ;
 
    loginProperty.setServer('LocalServer');
    loginProperty.setDatabase('AXDB');
    loginProperty.setUsername('bmssa');
    loginProperty.setPassword('bmssa_pwd');
    con = new ODBCConnection(loginProperty);
    sqlString =   "SELECT * FROM LedgerTrans WHERE DATAAREAID=' "
                         curExt()
                         "' "
                         + " AND TRANSDATE<CAST(' "
                         + date2str(str2date('22/02/2006',123),321,2,3,2,3,4)
                         + " ' AS datetime) "
                         + " AND TRANSDATE> CAST(' "
                         + date2str(str2date('20/02/2006',123),321,2,3,2,3,4)
                         + " ' AS datetime) ";
    stmt = Con.createStatement();
    resultSet = Stmt.executeQuery(sqlString);
    resultSet.next();
    result = resultSet.getString(1);
    if (result)
    {
        info(result);
    }
    else
    {
        info( "No record! " );
    }

}
Hope it helps.
 
March 16

Read and alter registry settings in Axapta

Here is a sample code of increasing max. open cursors during run-time. But, please note that its purpose is only to show you how to read and write registry. If you encounter any open cursors problem, it is recommended to optimize the SQL statement rather than changing the setting for maximum open cursors. 
static void IncreaseMaxOpenCursors(Args _args)
{
    #winapi
    container   regRet;   
    int         readReg;
    int         writeReg;
    str         keyValue, keyPath, configName;
    int         maxOpenCursors;
    ;
 
    //Specify your Axapta configuration utility name here
    configName = "LocalMachine";
    //New maximum open cursor, default is 90
    maxOpenCursors = 120;
    keyPath = @"SOFTWARE\Navision\Axapta\3.0\" + configName;
    readReg  = WinAPI::regOpenKey( #HKEY_CURRENT_USER, keyPath, #KEY_READ);
    if (readReg)
    {
        regRet = WinAPI::regGetValue(readReg, 'opencursors');
        keyValue = conpeek(regRet,1);
        info(keyValue);
        if (str2int(keyValue) < maxOpenCursors)
        {
            writeReg  = WinAPI::regOpenKey(#HKEY_CURRENT_USER, keyPath, #KEY_WRITE);
            if (writeReg)
            {
                WinAPI::regSetValueEx(writeReg, 'opencursors', #REG_SZ , maxOpenCursors);
                WinAPI::regCloseKey(writeReg);
            }
            Info("Max open cursors: " + int2str(maxOpenCursors));
        }
        WinAPI::regCloseKey(readReg);
    }
}

 

Axapta build number

In Axapta, choose the option About Microsoft Business Solution-Axapta in the Help pull-down menu, you can see the Axapta build number information, e.g. Microsoft Business Solution-Axapta 3.0 Build # 1951.6710/514-320 SP4/OP023-196.
What does it stands for?
The first part 1951.6710 is the kernel build number.
The second part 514-320 SP4 is the application build number.
The last part OP023-196 is the localization build number.
March 09

Tips on optimizing primary index in Axapta

Primary index plays an important role in gaining optimum performance in Axapta. The Primary Index of a table is the main index that is used to uniquely identify records in it. No duplicated values are allowed in this index. When caching records, primary index is used as the caching key if it exists.
When you designing a table's primary index, follow these rules:

  1. Keep the "width" of your indexes as narrow as possible. This reduces the size of the index and reduces the number of disk I/O reads required to read the index, boosting performance.

  2. If possible, try to create indexes on columns of Integer data type instead of string.  Integer values have less overhead than string values.

  3. Don't use Real data types for primary keys, as they add unnecessary overhead and can hurt performance.

  4. Indexes on narrow columns are preferable to indexes on wide columns. The narrower the index, the more entries SQL Server can fit on a data page, which in turn reduces the amount of I/O required when accessing the data.

  5. Reduce the size of the index, thus decreasing read I/O during the join process, and increasing overall performance.
March 03

Development Features of Dynamics Ax (Axapta) 4.0

Digested from Microsoft specification:

Common Language Runtime (CLR) Interoperability
The CLR Interop feature allows X++ developers to add CLR assemblies to the AOT and write X++ code that interoperates with objects in these assemblies.
This provides a number of benefits:
• Developer productivity It is more productive to support CLR assemblies than to require developers to create assemblies that they then wrap as COM components.
• .NET support Providing an easy way for developers to use their CLR components in Microsoft Dynamics AX is a highly desirable feature.
• ISV integration ISVs are encouraged to use .NET to write applications and to expose their application APIs using .NET technology such as XML Web services and remoting. X++ developers should be able to use these APIs directly from X++.
Version control
Because the application layer framework is insufficient for this purpose, version control is a common request from partners to help them protect their intellectual investment in customizations .
Microsoft Dynamics AX supports Microsoft Visual SourceSafe 6.0. However, some partners will want to support their own version control system. Extensibility hooks are provided and work with Version Control ID Server. Microsoft Dynamics AX Object IDs and Label IDs are issued. The IDs must be unique, and a central service should be used to coordinate issuing these IDs.
For general information about Microsoft Visual SourceSafe, see http://msdn.microsoft.com/vstudio/previous/ssafe/default.aspx
Debugger enhancements
In Microsoft Dynamics AX 4.0, the debugger is a stand-alone application and contains several new features for efficiently finding and fixing common problems in code, such as callstacks, breakpoints, and the ability to move the current execution point while executing code. Additionally, the user interface and shortcut keys follow the familiar design of Microsoft Visual Studio.
Compiler optimization
The compiler redesign reduces compilation time by approximately forty percent.
Data model reverse engineering using MorphX
Reverse engineering using MorphX enables partners to easily get detailed information about the structures and relationships of the Microsoft Dynamics AX business logic and data model.
The reverse-engineering feature is a replacement of the existing Visual MorphXplorer used to visualize the Microsoft Dynamics AX data model by drawing Entity Relation Diagrams for tables and classes. The goal is to simplify collection, extract relationships, and integrate and view collections such as Microsoft Office Visio diagrams as unified modeling language (UML) diagrams. This feature will handle reverse engineering of both the data model and the object model.
By integrating the data-model and object-model evaluation with Microsoft Office Visio UML diagramming, Microsoft Dynamics AX gives partners a familiar out-of-the-box tool to use for true modeling and simplified deployment.
ImageGear library replaced with GDI+
Microsoft Dynamics AX now uses the Microsoft GDI+ graphics library instead of the Accusoft ImageGear graphics library. The GDI+ library is an application programming interface (API) that is exposed through a set of C++ classes.
The GDI+ subsystem is a native component of the Microsoft Windows XP and Microsoft Windows Server 2003 operating systems and is available for 32-bit and 64-bit Microsoft Windows-based applications.
GDI+ allows application programmers to display information on a screen or printer without concern for the details of a particular display device. GDI+ insulates the application from the graphics hardware, and it is this insulation that allows developers to create device-independent applications.
Print drilldown
The print framework allows users to preview report content before actually printing the report. The print preview concept has been extended from being view-only to support drilling down into the transaction hierarchies and related transactions.
Print drilldown features include support based on Menu Item types (Output and Display), and multiple drilldown options on each report field.
Filtering function
Improved filtering of records in tables is accomplished through a new easy-to-use, embedded filter.
Country consolidation
To meet the expectations of our partners and customers, country-specific features will be consolidated in Microsoft Dynamics AX as follows:
• Western Europe, North America, and Asia Pacific regions  Local features for the following countries are merged into the SYS layer or have a combined layer that covers these regions in one installation: Australia, Austria, Belgium, Canada, Denmark, Finland, France, Germany, Ireland, Italy, Malaysia, Mexico, Netherlands, New Zealand, Norway, Singapore, South Africa, Spain, Sweden, Switzerland, Thailand, United Kingdom, and United States.
• Eastern Europe  Local features for the following countries are consolidated in a separate layer: Czech Republic, Estonia, Hungary, Latvia, Lithuania, Poland, and Russia.
• Other countries  Local features for the following countries have stand-alone layers: Brazil, China, Iceland, India, Japan, and Turkey.
The Microsoft Dynamics AX globalization team will strive to incorporate all new legal requirements into Microsoft Dynamics AX as time permits.
Consolidation of country-specific features reduces deployment and customization complexity.

Pass complex data types to 3rd party DLLs in Axapta

If you are using third-party dll files, you may encounter a problem that you need pass a complex data types to or from the dlls, for example, struct, array etc. 
There is an example to show you how to pass struct data type to dlls.
Below is the api of a given dll.
/* C++ API – SampleDll.dll
typedef struct
{
LPCTSTR lpszFontName;
int nCharacterExtra;
int nFontHigh;
int nSpacing;
int nAlign;
LPCTSTR lpszTextString;
} BCTEXTINFO, *LPBCTEXTINFO;

BCENCODE_API DWORD WINAPI SampleDllMethod(LPBCTEXTINFO lpTextInfo);
*/
 
And we can use Binary class to store the string's address instead of including the string directly in the struct. Here is a sample code to show you how to pass a struct to dll.
    client static int SampleMethod(str _name, int _chExtra, int _fontHeight, int _spacing, int _align, str _content)
    {
        int ret;
        Binary lpTextInfo  = new Binary(24);  // 6 * 4 = 24
        Binary biName     = new Binary(strlen(_name) + 1);
        Binary biContent  = new Binary(strLen(_content) + 1);
 
        DLL _winApiDLL  = new DLL('SampleDll.dll');
        DLLFunction _SampleMethod = new DLLFunction(_winApiDLL,'SampleDllMethod');
        ;
 
        _ SampleMethod.returns(ExtTypes:: DWord);
        _ SampleMethod.arg( ExtTypes::Pointer, ExtTypes::Pointer);
        biName.string(0, _name);
        biContent.string(0, _content);
 
        lpTopTextInfo.binary(0, biName);
        lpTopTextInfo.dWord(4, _chExtra);
        lpTopTextInfo.dWord(8, _ fontHeight);
        lpTopTextInfo.dWord(12, _ spacing);
        lpTopTextInfo.dWord(16, _ align);
        lpTopTextInfo.binary(20, biContent);
 
        ret = _ SampleMethod.call(lpTextInfo);
        return ret;
    }
March 02

Use resource files in Axapta

Axapta provides a very handy feature to allow developers to ship their solution with Axapta built-in image files. In Application Object Tree, you can find resources node. Select resources node and right click; select Create from File, specify the file location for the new resource file. After that you can use this resource file in Axapta without specifying an absolute file path in your hard disk.
Then let's see how to use this kind of files in Axapta.
First, pick up the resource node from AOT;
SysResource::getResourceNode();
Then generate a temporary file for this resource file;
SysResource::saveToTempFile()
Finally specify the temporary file path for controls.
Here comes an example to show how to use a resource file as a background image of  a given form.
    {
        ResourceNode            resourceNode;
        FilePath  imagename;
        ;
        resourceNode = SysResource::getResourceNode(resourcestr(ResourceName)); 
        if (resourceNode)
        {
            resourceNode. AOTload();
            imagename =  SysResource::saveToTempFile(resourceNode);
        }
        else
        {
             throw Error("No file exists.")
        }
        element.design().imageName(imagename);
    }