Thursday, August 9, 2012

ok = ok ? ok : !ok

While working in CUS layer, I keep finding validate* methods that basically look like this:

public boolean validateWrite()
{
    boolean ok = true;
 
    ok = this.validateSomething() && ok;
    ok = this.validateSomethingElse() && ok;
    ...
    // johndoe 11.11.11 ->
    ok = this.validateWrittenByJohnDoe();
    // johndoe 11.11.11 <- 
 
    return ok;
}


As you can see, John forgot something: should validateWrittenByJohnDoe return true, the whole validateWrite would succeed, even if both validateSomething and validateSomethingElse failed. That may result in corrupted data.

There is only one case when skipping "&& ok" in the chain of validations is valid - when you explicitly set boolean variable to false:

    ok = checkFailed("@CUS1111");

Otherwise, either place an "ok" in front of your check, so that it would be skipped, if the result were false anyway:

    ok = ok && this.validateWrittenByJohnDoe();

or place it in the end, so all possible errors will be found and displayed in the infolog:

    ok = this.validateWrittenByJohnDoe() && ok;

Keeping all this in mind is especially important in AX 2012, where eventing is possible, as faulty event handlers on validate-method may lead to quite sneaky bugs.

Thursday, July 26, 2012

Parm-methods generator script

It may be boring to manualy create parm-methods for each an every class member variable, although there is an editor script for that. By the way, there is a bug-o-feature in AX 2012 editor, which does not allow you to select anything but an EDT for the variable type.

This is a script that auto-generates parm-methods for a class, based on its class declaration:
static void createParmMethod(Args _args)
{
    #AOT
   
    ClassName className = classStr(MyClass);  // <---------------- Write your class name here
   
    TreeNode classDeclarationTreeNode;
    TreeNode classTreeNode;
    TreeNode parmMethodNode;
   
    Source classDeclaration;
   
    System.Text.RegularExpressions.MatchCollection mcVariables;
    System.Text.RegularExpressions.Match mVariable;
    int matchCount;
    int matchIdx;
   
    System.Text.RegularExpressions.GroupCollection gcVariableDeclaration;
    System.Text.RegularExpressions.Group gVariableDeclarationPart;
   
    str variableType;
    str variableName;
   
    str pattern = ' (?<VarType>[a-zA-Z0-9_]+)[ ]+(?<VarName>[a-zA-Z0-9_]+);';   
   
    Source parmMethodBody;
   
    classTreeNode = TreeNode::findNode(strFmt(@"%1\%2", #ClassesPath, className));
   
    classDeclarationTreeNode = TreeNode::findNode(
        strFmt(@"%1\%2\ClassDeclaration",
        #ClassesPath,
        className));
   
    classDeclaration = classDeclarationTreeNode.AOTgetSource();
   
    mcVariables = System.Text.RegularExpressions.Regex::Matches(
        classDeclaration,
        pattern,
        System.Text.RegularExpressions.RegexOptions::Singleline);

    matchCount = CLRInterop::getAnyTypeForObject(mcVariables.get_Count());   
   
    for (matchIdx = 0; matchIdx < matchCount; matchIdx++)
    {
        mVariable = mcVariables.get_Item(matchIdx);
        gcVariableDeclaration = mVariable.get_Groups();
       
        gVariableDeclarationPart = gcVariableDeclaration.get_Item('VarType');
        variableType = gVariableDeclarationPart.get_Value();
       
        gVariableDeclarationPart = gcVariableDeclaration.get_Item('VarName');
        variableName = gVariableDeclarationPart.get_Value();
       
        parmMethodBody = new xppSource().parmMethod(variableType, variableName);
       
        parmMethodNode = classTreeNode.AOTadd('method1');
        parmMethodNode.AOTsetSource(parmMethodBody);
        classTreeNode.AOTsave();
    }
   
    classTreeNode.AOTcompile();
}

Tuesday, July 24, 2012

Regex looking for return calls within ttsbegin/ttscommit

Return calls should never appear within a ttsbegin/ttscommit pair. If they do, the application may eventually complain that


I have recently had to look for such an issue in a third-party code, and this is the job I used to looks for suspecious "returns", which did find one. Please note, that with the current regex pattern, there may be false positives, but in my case it would take more time to write a perfect pattern, than to manually look through those false positives:

 static void checkSourceForReturnsInTTS(Args _args)
{
    TreeNode treeNode;
    TreeNode sourceTreeNode;   
    TreeNodeIterator it;
    ProjectNode projectNode;
    TreeNodeTraverserSource traverser;
    Source source;
   
    System.Text.RegularExpressions.MatchCollection mcReturnsInTTS;  
    int matchCount;
    int matchIdx;   
   
    str pattern = 'ttsbegin.*[^a-z0-9_]return[^a-z0-9_].*ttscommit';
    str matchString;
   
    projectNode = SysTreeNode::getPrivateProject().AOTfindChild("MyProject");
    treeNode = projectNode.loadForInspection();
  
    traverser = new TreeNodeTraverserSource(treeNode);
    while (traverser.next())
    {
        sourceTreeNode = traverser.currentNode();
       
        source = sourceTreeNode.AOTgetSource();
        source = System.Text.RegularExpressions.Regex::Replace(
            source,
            '[/][*].*[*][/]',
            '',
            System.Text.RegularExpressions.RegexOptions::Singleline);
        source = System.Text.RegularExpressions.Regex::Replace(
            source,
            '[/]{2,}.*\n',
            '');       
       
        mcReturnsInTTS = System.Text.RegularExpressions.Regex::Matches(
            strLwr(source),
            pattern,
            System.Text.RegularExpressions.RegexOptions::Singleline);
       
        matchCount = CLRInterop::getAnyTypeForObject(mcReturnsInTTS.get_Count());
        if (matchCount > 0)
        {
            info(sourceTreeNode.treeNodePath());
        }
    }   
}

Wednesday, July 18, 2012

The X++ debugger does not open...

... although you turned off "Execute business operations in CIL" in the "Tools > Options".

Then there is probably a runAs(...) method somewhere down the call stack.

You can find such a runAs-call by setting a breakpoint in Visual Studio, attaching to the process and running your logic again. After the runAs-call is found, you can simply go replace it with a direct static method call.

For example:


After this change, the X++ debugger will stop where needed.

Remember - this change is only for debugging purpose. You should not do that in the production code.

Monday, July 16, 2012

If you forgot to fix DataSource property on a grid,...

... although its child controls are set up properly, then your form may behave in a weird way.

For example, we have a form with 2 linked datasources (SalesTable and SalesLine) and 2 grids accordingly:


The second grid, SalesLineGrid, has DataSource property set to SalesTable. That value was set by default at the moment the grid control was created. You can also see 3 controls in the SalesLineGrid, but their DataSource properties are set to SalesLine, so they are fine.

Now, if we open the form and switch between sales orders back and forth, the bottom grid with sales order lines will not work properly:


As you can see, the cursor position in the second grid changes in sync with that of the first grid, there are a lot of "empty" sales order lines, and, finally, the rendered sales order lines are actually wrong.

Going back to the form in the AOT and setting the SaleLineGrid datasource property to SalesLine will fix the issue:

Wednesday, July 4, 2012

AIF: Mind auto-generated Axd<DocumentName>.findCorrespondingAxBC method

Problem

You have just created a document service with the wizard. You need the document to contain an unbound value. So, you create a display method on one of the tables, and add a corresponding parm-method to Ax<TableName> class. Then you refresh the services, but the new field is not presented in the schema.

Solution

Check the new Axd<DocumentName> class. By default, it may have findCorrespondingAxBC method, overriding the base class method and always returning classnum(AxCommon). This is why your new parm-method in the Ax<TableName> class is ignored.

P. S. There are actually TODOs added to the auto-generated methods. It may be a good idea to clean up the TODOs first, and then continue with the service development.

Wednesday, June 27, 2012

What would happen to my event handlers, if the sys-layer method disappeared?

The other day I explained to my colleagues, what the benefits of events and models, introduced in AX 2012, were.

That time, I was asked a question "What would happen to my event handlers, if the method they are attached to in the AOT was removed in the next release?" I could not give a precise answer, but I was sure that nothing terrible would happen in that case.

Let's check what would actually happen. First, let's create 2 models, Bands and Fans:


Now, let's add class TheBeatles to the Bands model, and class Fans to the Fans model. On top of that, we will create two event handers in the Fans model, so Fans would shout whenever TheBeatles gave a concert, and buy albums whenever TheBeatles recorded one:



More details:





If you run TheBeatles::main method now, the infolog will look like this:


Let's export the Fans model to save the original version of the Fans class:


OK. Let's assume now that TheBeatles stopped giving concerts (hell, no...). We have to delete the giveConcert method from TheBeatles class:


As we can see, the fansShoutEventHandler is gone, too. More than that, it does not exist in the model either:


But, we have our exported Fans model. Let's import it back and see what will happen:


The import failed, as the model file referenced a non-existing element, giveConcert method. Let's run the same import command, but with "/createparents" parameter, as it is proposed in the error message:


As we can see, a virtual model has been created to help the Fans model get imported properly. In the AOT, TheBeatles class looks like this now:


giveConcert method is there again, now in the "Fans (Virtual 1)" model.

In the end, we can decide what to do with the fansShoutEventHandler - remove it at all, move it somewhere else or maybe refactor.

For more information on how the events and models can save you time, I would recommend this blog.