Saturday, April 30, 2011

Query forceSelectOrder and forceNestedLoop hints are not saved when you pack and unpack a query

In some situations it's useful to take a query from a form or a calling class, transform this query somehow and use in another part of the application. For instance, you might take a query from a form datasource to process all the records queried by the form, or take a query and transform it to count records that can be fetched by the query (what SysQuery::countTotal() does), or just pass a query object from one tier to another (client to server). In all those cases you would do something like this:

Query newQuery = new Query(oldQuery.pack());

Yes, I know that in case of a form datasource query you should also take care of dynalinks but this is not the point. The point is that you might not get the same execution plan for the resulting query because forceSelectOrder and forceNestedLoop hints are not preserved during query pack/unpack. This is true at least for the AX 2009 SP1 RU7 kernel (5.0.1500.4570) so for now if you use these hints you should not rely solely on the kernel when you copy a query. For this purpose you can create a SysQuery method like this:

/// <summary>
/// Copies a query including a one with dynalinks and hints
/// </summary>
/// <param name="_q">
/// source query
/// </param>
/// <param name="_copyHints">
/// must the forceSelectOrder and forceNestedLoop hints also be copied
/// </param>
public client server static Query copy(
    Query   _q,
    boolean _copyHints = true
    )
{
    QueryBuildDataSource    qbdsOld;
    QueryBuildDataSource    qbdsNew;
    QueryBuildDynalink      qbdl;
    QueryBuildRange         qbr;
    Query                   ret;
    str                     sq;
    Counter                 n;
    ;
    if (!(_q && _q.dataSourceCount()))
    {
        throw error(Error::wrongUseOfFunction(funcname()));
    }
    ret = new Query(_q.pack(false));
    if (_copyHints)
    {
        // Query methods forceSelectOrder() and forceNestedLoop() have mandatory parameters
        // so you cannot use these methods as ordinary properties to find out current values
        sq = _q.dataSourceNo(1).toString();
        if (match(@"^SELECT WITH.* SELECT_ORDER[ ,]", sq))
        {
            ret.forceSelectOrder(true);
        }
        if (match(@"^SELECT WITH.* NESTED_LOOP[ ,]", sq))
        {
            ret.forceNestedLoop(true);
        }
    }
    qbdsNew = ret.dataSourceNo(1);
    qbdsOld = _q.dataSourceNo(1);
    for (n = 1; n <= qbdsOld.dynalinkCount(); n++)
    {
        qbdl = qbdsOld.dynalink(n);
        // clear all existing ranges for this field
        while (qbdsNew.findRange(qbdl.field()))
        {
            qbdsNew.clearRange(qbdl.field());
        }
        // set the range value by the current dynalinked cursor field value
        qbr = qbdsNew.addRange(qbdl.field());
        qbr.value(queryValue(qbdl.cursor().(qbdl.dynamicField())));
        qbr.status(RangeStatus::Locked);
    }
    return ret;
}

AX 2009 client freezes and doesn't redraw its window during long operations (FIX!)

Hey, it's been a long time since the last post!.. Anyway, when you run a long lasting operation in AX 2009, after about 5 seconds a client window would blink - and bam! it's in the Not Responding state and doesn't redraw anything. This behavior has been annoying me since I've first seen AX 2009 but now there seems to be a solution! I've found it accidentally in this post of the EMEA Dynamics AX Support blog. Here what they say:

...One reason for running into this issue can be if the Windows Operating System is replacing the Dynamics AX application window by a ghost window. When Dynamics AX starts a lengthy COM operation, it is not responding to messages sent by the Windows Operating System in time. So Windows supposes Dynamics AX has stopped responding. When this happens the Dynamics AX application window is replaced by a ghost window until Dynamics AX resumes. Window ghosting is a nice feature that allows the user to minimize, move or close the main window even if the application is not responding. You can easily identify a ghost window as it shows (Not responding) in the window title.

One of the proposed solutions is to run a client in the WinXP SP2 compatibility mode, but it requires you to create a special shortcut for it and run every deployed client with such a shortcut, which is not always convenient. But theres's also another way.

The Win32 API function DisableProcessWindowsGhosting can disable windows ghosting feature for the calling process and you can customize your Dynamics AX application to make the client call this function on startup. All you have to do is add a new method to the WinAPI class:

/// <summary>
/// Call user32.DisableProcessWindowsGhosting
/// </summary>
/// <remarks>
/// Disables the window ghosting feature for the calling GUI process. Window
/// ghosting is a Windows Manager feature that lets the user minimize, move,
/// or close the main window of an application that is not responding.
/// </remarks>
public static client void disableProcessWindowsGhosting()
{
    DLL         dll     = new DLL(#UserDLL);
    DLLFunction dllFunc = new DLLFunction(dll, @"DisableProcessWindowsGhosting");
    ;
    dllFunc.returns(ExtTypes::void);
    dllFunc.arg();

    dllFunc.call();
}

And then call it say from info.startupPost():

/*
No SYS code must exist in this method
*/
void startupPost()
{
    if (clientKind() == ClientType::Client)
    {
        // BP deviation documented
        WinAPI::disableProcessWindowsGhosting();
    }
}

This Win32 API function is supported in at least WinXP SP3 and Windows Server 2003 SP2. It's a pity windows ghosting is not disabled by the client kernel itself.