By Tina

Dynamics and Visual Studio error: “The breakpoint will not currently be hit.”

If you see the following error while adding a breakpoint to X++ code in Visual Studio:
The breakpoint will not currently be hit. No symbols have been loaded for this document.

And your breakpoint is empty with a small exclamation mark next to it:
Breakpoint disabled

Enable the parameter to allow Visual Studio to load symbols for items that are not in your current solution:

Tools> Options> Dynamics AX> Debugging
parameters

This will enable the breakpoints in the code:
breakpoint enabled

Once you start debugging your breakpoints in the breakpoints window will also turn from empty to red, and they will be hit when the code is executed:
Breakpoints

I hope this solves the error! If it does, or it doesn’t, let me know in the comments.

Useful commands for AX 7

Today marks my first day offical day as AX 7 (Dynamics 365 for Operations) developer. To celebrate I though I would post two commands that have come in useful so far:

Internet Information Services (IIS)

  • iisreset /stop
  • iisreset /start
  • iisreset /restart

To run the commands, press Windows key + R, type them in and press enter.

iisreset

These commands stop, starts or restarts your server. Think of it as the same commands you used to do in Services to stop and start the AOS.

Visual Studio

  • devenv /resetuserdata

This command can be run in the Visual Studio Command prompt:

vs-cmd

This command deletes the user preferences for Visual Studio.

So far we’ve tried it for every issue we’ve run into: Table browser not working, EDT editor not opening and so on and so forth. It has not solved any of our issues till now, but I thought I would put it out there. If it solves some inexplicable issue you have with Visual Studio and AX 7, please let me know in the comments!

X++: Create a list of child classes or find extended methods

Jet another small X++ job: This job shows a list of child classes per parent class.

If you want so see the parent-child relationship in AX2012, the best way is to Right click on the class > Add-ons > Type hierarchy browser.

find class relation

The Type hierarchy browser shows the relationships between parent and child classes as well as the methods in each class. While this is great functionality I use regularly, I found it a bit difficult to see which child classes inherit or override a particular parent method.

Type hierarchy browser

I wanted to add a parameter variable to the header of one of the invent movement child methods. Parent and child methods in X++ must have exactly the same parameters. This means if you want to add a parameter to a child method you must also add it to the parent method and to all the other children that over rides that particular method. The list below shows me how many child classes I need to modify if I want to add a parameter to one child method.

For example, the InventMovement class has 85 child classes. Only 24 of those children overrides the method “DefaultDimension”. If you want to pass an extra boolean parameter to any of those 24 “DefaultDimension” methods, you have to add it to the definition of all 24 methods and to the parent.

This job creates a list of the 24 classes that overrides the “DefaultDimension” method in the InventMovement class.

My first instinct was to use the SysDictClass’s hasObjectMethod method, but since the method is available on all children through inheritance, it always evaluate to true.

  1. if(childDictClass.hasObjectMethod(methodStr(InventMovement, DefaultDimension)))

Instead, you need to check if the method “DefaultDimension” is a method node under the class node in the AOT.

  1. //Tina van der Vyver
  2. //makecreatereiterate.com
  3. static void showExtendedMethods(Args _args)
  4. {
  5.     #define.ClassPath(@"\Classes\%1")
  6.  
  7.     SysDictClass    dictClass;
  8.     List            extendedClassList;
  9.     ListEnumerator  listEnumerator;
  10.     ClassName       className;
  11.     MethodName      methodName;
  12.     ClassId         childClass;
  13.     TreeNode        childClassNode;
  14.     TreeNode        methodNode;
  15.     int             numberofMethods;
  16.  
  17.     //set parent class and method you want to investigate
  18.     className           = "InventMovement";
  19.     methodName          = "DefaultDimension";
  20.  
  21.     dictClass           = new SysDictClass(className2Id(classname));
  22.     extendedClassList   = dictClass.extendedBy();
  23.  
  24.     //loop through all child classes
  25.     if (extendedClassList.elements())
  26.     {
  27.         listEnumerator = extendedClassList.getEnumerator();
  28.  
  29.         setprefix(strfmt("Class %1 extended by %2 classes.", className, int2str(extendedClassList.elements())));
  30.  
  31.         while (listEnumerator.moveNext())
  32.         {
  33.             childClass      = listEnumerator.current();
  34.             childClassNode  = TreeNode::findNode(strFmt(#ClassPath, classId2Name(childClass)));
  35.  
  36.             //Try to find a method node for the method you are looking for
  37.             methodNode  = childClassNode.AOTfindChild(methodName);
  38.  
  39.             if (methodNode)
  40.             {
  41.                 numberofMethods++;
  42.                 info(ClassId2Name(childClass));
  43.             }
  44.         }
  45.         info(strfmt("%1 child classes override the method '%2'.", numberofMethods, methodName));
  46.     }
  47. }

child class x++

List of table fields and types in CSV file

I wrote a job today to create a list of fields in a table and each field’s data type. You specify a table name in the variables, and it he job exports a list of non-system fields in the format: “Displayed name” (label), “Technical Name” and “Data Type” to CSV file.

Notice (line 50 – 70) that it lists the possible enum values for enum fields. It also adds the string length in brackets for string fields.

  1. //Tina van der Vyver
  2. //makecreatereiterate.com
  3. static void ListFields(Args _args)
  4. {
  5.     #File
  6.     DictField           dictField;
  7.     DictTable           dictTable;
  8.     DictType            dictType;
  9.     DictEnum            dictEnum;
  10.     FieldId             fieldId;
  11.     Types               type;
  12.     TableName           tableName = tableStr(BatchJob);
  13.  
  14.     str                 enumValues, stringLength, stringType;
  15.     int                 i;
  16.  
  17.     CommaTextIo         commaTextIo;
  18.     FileIOPermission    permission;
  19.     str                 fileName = strFmt(@"C:\%1.csv", tableName);
  20.     ;
  21.     permission = new FileIOPermission(fileName, #io_write);
  22.     permission.assert();
  23.  
  24.     commaTextIo = new CommaTextIo(fileName ,#io_write);
  25.     dictTable   = new dictTable(tableName2Id(tableName));
  26.     fieldId     = dictTable.fieldNext(0);
  27.  
  28.     if (fieldId)
  29.     {
  30.         commaTextIo.write("Displayed name", "Technical Name", "Data Type");
  31.     }
  32.  
  33.     while (fieldId)
  34.     {
  35.         dictField = new DictField(tableName2Id(tableName), fieldId);
  36.  
  37.         if (dictField && !dictField.isSystem())
  38.         {
  39.             type = dictField.baseType();
  40.  
  41.             switch (type)
  42.             {
  43.                 case Types::String:
  44.                     dictType        = new DictType(dictField.typeId());
  45.                     stringLength    = (strFmt("%1[%2]",type, dictType.displayLength()));
  46.  
  47.                     commaTextIo.write(dictField.label(), dictField.name(), stringLength);
  48.                     break;
  49.  
  50.                 case Types::Enum:
  51.                     dictEnum = new DictEnum(dictField.enumId());
  52.  
  53.                     for (i = 0; i < dictEnum.values(); i++)
  54.                     {
  55.                         if (i == 0)
  56.                         {
  57.                             enumValues = "Enumeration: ";
  58.                         }
  59.                         else
  60.                         {
  61.                             enumValues += "/";
  62.                         }
  63.  
  64.                         if (dictEnum.value2Name(i))
  65.                         {
  66.                             enumValues += dictEnum.value2Name(i);
  67.                         }
  68.                         else
  69.                         {
  70.                             enumValues += dictEnum.value2Name(i);
  71.                         }
  72.                     }
  73.  
  74.                     commaTextIo.write(dictField.label(), dictField.name(), enumValues);
  75.                     enumValues = '';
  76.                     break;
  77.  
  78.                 default:
  79.                     stringType = strFmt("%1", type);
  80.                     commaTextIo.write(dictField.label(), dictField.name(),  stringType);
  81.                     break;
  82.             }
  83.         }
  84.         fieldId = dictTable.fieldNext(fieldId);
  85.     }
  86.     CodeAccessPermission::revertAssert();
  87. }

The example above exports the fields for the “BatchJob” table. In AX 2012 R3, it generates this CSV file:

List of fields

Number sequence error when starting the Upgrade checklist

Last week we solved a rather annoying issue that popped up in a clean ready-for-upgrade installation. After following the steps in the “AOD code upgrade checklist”, we suddenly saw the error ‘The number sequence for party records is not set.’. It appeard after we’ve imported the ISV, VAR and USR models. After the error is shown, the upgrade checklist is not displayed again.

AOD code upgrade checklist

Checklist in question: AOD code upgrade checklist.
Checklist AOT name: SysCheckList_UpgradeCode.
Database: Empty database with ‘Register database for upgrade‘ enabled during installation.
Error appearance: When the client is started after the ‘Import ISV upgraded layer model(s) into new model store‘ step was executed.

The problem:
It turns out the offending code was in the ISV model in the startupPost() method on the Info class.

To see an example of how the startupPost() can be used, take a look at this detailed post by Nayyar Siddiqi. Like Nayyar’s solution our ISV model has a piece of code changes application behaviour during startup. Inside the startupPost() method was a find method that selects the current company and adjusts the menu’s shown based on the company’s setup.

The code in the Info class:

  1. /*
  2. No SYS code must exist in this method
  3. */
  4. void startupPost()
  5. {
  6.     //find the current company when the client is stared:
  7.     CompanyInfo companyInfo = CompanyInfo::find();
  8.     //code that changes menu items based on company setup...
  9. }

The problem with this is of course that a ready-for-upgrade installation does not contain any data. While trying to access the record in the CompanyInfo table, the application tries to make a new record in the DirPartyTable. Since it is an empty installation, no number sequence has been setup for the DirPartyTable, and the error is thrown.

InitValueDirParty

The solution in the Info class:
To resolve this issue, you first need to check if the find call is made in a ‘normal’ environment, or an environment in install or upgrade mode, before you do any method calls to access data.

Simply add a check to make sure the application is in running mode, and not in install mode or busy starting the upgrade mode:

  1. /*
  2. No SYS code must exist in this method
  3. */
  4. void startupPost()
  5. {
  6.     CompanyInfo companyInfo;
  7.     Application app = new Application();
  8.  
  9.     if (app.isRunningMode() && !SysModelStore::isInstallMode() && !SysCheckList_Update::isUpgradeMode())
  10.     {
  11.         companyInfo = CompanyInfo::find();
  12.         //code that changes menu items based on company setup...
  13.     }
  14. }

Second year software developer anniversary

I’ve officially been a Microsoft Dynamics Ax developer for 2 years! :-)

All and all it’s been a good development year. I’ve had the opportunity to go to two clients on-site, I attended the Development 3 training and a workshop at Microsoft.
With the support of seniors and management, I have come a long way with Dynamics AX in the last two years. At the same time, if you believe Peter Norvig, I have at least 8 more years to truly become a developer!

Last year I was extremely enthusiastic about my Dynaniversary and posted a celebratory post exactly on the 1st of March. I also wrote a second post two days later detailing my goals for the year. While this anniversary post may be 31 days late, I figured since it was still in the month of March, I am allowed a short reflection on the past year.

What I learned this year (in a nutshell):

  1. Synchronization in Dynamics AX is dangerous
    Let’s just say I learned that if the synchronization is taking way longer than it should be taking, it might be because it’s deleting every record in the database. #dataUpgradeIsFun
  2. Politics are important
    And so is management and project managers and other client-facing roles in the team. As a developer at a software company, you may feel you are the one contributing to the core business activity and you have the most important role to play. This year I learned and saw the importance of client-facing roles to get customers and users on board with the software and the processes.
  3. Regular Expressions
    Jamie Zawinski famously said; “Some people, when confronted with a problem, think “I know, I’ll use regular expressions.” Now they have two problems.”. This happened to me this year, and I was left a bit disappointed with regex support in X++ (at least with the TextBuffer class).
  4. Unit Testing
    I read up all I can about unit testing from Microsoft and other resources. I had the opportunity to share my finding with our development team.
  5. Manual Testing
    I was asked to look into the options of automating our manual testing process. I am looking into third party testing automation tools build in Dynamics AX as well as Microsoft Test Manager. I went to a testing workshop at Microsoft Schiphol (NL) offices last week, and will unquestionably share a bit more about this in the future.

Review of last year’s goals:

  1. Understand InventoryPartly achieved
    I no longer quiver with fear when I hear “InventDim” or “InventSum” but I still have a long way to go. While I spend hours writing queries for a reservation type form based on inventSum, and can mostly figure out isolated issues with inventory, I don’t feel I have a good overview of the entire module and certainly wouldn’t feel comfortable explaining it to anyone.
  2. Learn the technical side of thingsPartly achieved
    I still haven’t read part 1 of ‘Inside Microsoft Dynamics AX’, but I have read isolated chapters in the book. I however do have a pile of whitepapers on my desk that I’ve read, including ‘Introduction to the SysOperation Framework’, ‘Testing Best Practices’, ‘Deploying customizations across Microsoft Dynamics environments’ and ‘Upgrade best practices’.
    I have also read a few chapters from ‘Code Complete 2’; a book on software construction I can recommend highly.
  3. Ask less help Partly achieved
    I have definitely improved a lot with this issue and disturb senior developers much less than in the past. Still, after reading this post by Ross Williamson, I feel I should continue working on this and strive to figure more things out for myself.
  4. Blog at least once a week Failed
    My last post before this one was two months ago; I definitely need to get my 22 draft posts published…
  5. Join a development real live communityFailed
    I haven’t joined a group yet; I need to work on this for next year.

Goals for next year:

  1. Learn the system technically and functionally (finally read ‘Inside Dynamics Ax’, learn MVC and SysOperation, understand inventory, the product model and of course AX7).
  2. Blog more.
  3. Do Powershell (I have this book, I have to read it and then use it).
  4. Be less awful by this time next year. (à la Steve Yegge and Jeff Atwood)
  5. Above all, be a humble programmer. (à la Edsger W. Dijkstra)

Be a humble programmer? The thing is, the more I learn, the greater the chance that I’ll lose sight of how much I don’t know. I’ll conclude with this quote by Dijkstra – may we all strive to learn more, stay humble and respects the limits of our own abilities:

We shall do a much better programming job, provided that we approach the task with a full appreciation of its tremendous difficulty, provided that we stick to modest and elegant programming languages, provided that we respect the intrinsic limitations of the human mind and approach the task as Very Humble Programmers. – Edsger W. Dijkstra in The Humble Programmer (1972)

X++ “Cross reference” for extended data types

I wrote a job to check which fields uses a specific enum. This week I needed something similar for an extended data type and I modified it to work with EDTs instead.

The cross reference on the environment I am currently working on is not up to date. I used this job to find all the table fields that use this extended data type, similar to what the standard cross reference function does.

Simply change the extended data type in line 5 to the EDT you want to investigate, and the job makes a list of table fields that uses the EDT. You can also change the layer depending on what you are looking for.

For example, for InterCompanyCompanyId, searching through all the fields in the SYS layer:

  1. //Tina van der Vyver
  2. //makecreatereiterate.com
  3. static void edtCrossReference(Args _args)
  4. {
  5.     str                 edtName = extendedTypeStr(InterCompanyCompanyId);
  6.     ExtendedTypeId      edtId = extendedTypeName2Id(edtName);
  7.     UtilIdElements      utilfield;     
  8.     DictField           dictField;       
  9.  
  10.     setPrefix(strFmt("EDT: %1", edtName));
  11.  
  12.     while select parentId, utilLevel, Name, id from utilfield
  13.         where utilfield.recordType == UtilElementType::TableField        
  14.         && utilfield.utilLevel  == UtilEntryLevel::sys
  15.     {
  16.         dictField = new DictField(utilfield.parentId, utilfield.Id);
  17.  
  18.         if (dictField.typeId() == edtId)
  19.         {      
  20.             info(strFmt("%1: %2 - %3", utilfield.utilLevel, tableId2name(utilfield.parentId), dictField.name()));
  21.         }
  22.     }
  23. }

IntercompanyCompanyId crossreferance

Development III training, Day 2

I am back with my notes from development III training.

Today was ‘interesting’ since the trainer would tell us things that are not true with the hope that it will encourage us to test and figure things out for ourselves.

I guess it’s a good strategy to get us to become better critical thinkers and self-learners. It has left me with a whole list of things to try for myself since I have stopped believing anything he says. :-)

Without further ado, here are three things I learned today:

Create a project with all the temporary tables.

If you want to create project with a list of all the temporary tables in the AOT, you have a few options:

  1. Create a new project and use the project filter to filter tables that contain ‘tmp’ in the name. Keep in mind, that this method assumes that all temporary tables contain ‘tmp’ in the table’s name (This might be a best practice, but someone could have ignored it.)

filter

Another problem with this method above is that it is not dynamic. If you create a new temporary table outside of the project, it will not be added to the project automatically.

  1. Create a tables node in a project and set its Group Mask property to ‘tmp’. This will create a filter for the tables node so that it contains all the tables that contain ‘tmp’ in the name.

group mask

This still does not solve the problem that another developer can create a normal table called MytmpDataTable or a temporary table named MyData . The group mask still only filters on the table name.

An advantage with the group mask is that the tables are dynamically added. Any new or renamed tables will be added to the tables node in the project.

While the temporary table example clearly has drawbacks, you can still use the group mask function when you want to filter based solely on a object’s name. For example, you could create a module project that contains all tables starting with ‘EcoRes’ or ‘Hcm’.

  1. The third, and best option (in my opinion) is to create a job that checks each table’s properties and if it is a temporary, it added to a project.
    This assures that all the tables in the project are really temporary tables, and it is ‘dynamic’ since you can always rerun the job. :-)

The AX database model
In general the AX data model is a normalized data model. One of the exceptions is the Parm tables. Parm tables are based on a Star(or snowflake) model.

In practice this means that they improve performance, reduces locking of important records but contains duplicate data also found in other tables.

Since all the data in the parm table is stored in other tables in the system, the data can be deleted since it can always be recreated with its source table’s status is changed.

Server client void..
I noticed this method during class:

class modifier

Adding the sever method modifier forces the method to be run on the server, while adding client does the same but for the client. So how can you force a method to run on both the client and server?

Turns out “you can use both client and server to change the execution place (to Called from) of a class static method or to document that it is decided that a table method executes best as Called from.

Of course the server and client method modifiers can only be used on static and table methods. If the method is not static, you need to specify the location using the class property RunOn. Non-static class methods run where their class was instantiated.

AX Game design
We received an assignment to write a game with the concepts we learned in class like the map class, tmp tables and sets. I’ll be back to share my version of ‘Hangman’ in Dynamics soon!

If you have questions about these topics I would love to discuss them in the comments.

Developer III Training, Day 1

Dynamics AX Development training consists of six courses from an introduction to AOT objects and X++ code, to developing Windows apps that connect to Dynamics AX.

I completed Development I and II in my first two weeks as an AX developer and found it incredibly overwhelming. (It might also have something to do with the fact that it was presented in Dutch, and I am not Dutch… but who knows?)

I was fortunate to be sent on the Dynamics AX Developer III training this week. Dev III consists out of four days spread over two weeks. I’ve completed day one today and it has been great! I have learned a lot (but not so much as to feel completely overwhelmed) and this time I even understand and speak Dutch. :-)

Here are a few unrelated points I jotted down in class today. They are not necessary related to the topics in the official material but are nevertheless things I would like to remember:

Dynamics AX development wizards
Dynamics AX had a few wizards that can be used to create objects in the AOT. The wizards are found under Tools > Wizards.
wizard
For example, the class wizard can be super useful, since it can create a child class with all the abstract methods in the parent.

SysStartUpCMD
It is possible to start the AX client via command line and specify a startup command line parameter like synchronize, checkbestpractices and compileall.

For example, if you have a Unit Test project (Unit tests are chapter 1 in Dev III), you can start the client with the command prompt and your project name. In this case you must also add a XML listener to the Unit Test Parameters. This will run your tests in the client and write the results to a XML file.
Command promp

Also… did you know you can write your own start up commands?! Startup commands are handled by a standard AX class named SysStartupCmd. In the construct method, you can see all the available start up commands in a switch() statement, and you can customize it to add your own client startup commands.
startup

Table inheritance
I got entirely caught up in the idea that when tables extend other tables, they inherit fields from the parent table. It never occurred to me that table inheritance also applies to the table methods. In other words, a child table inherits all the methods from the table it extends from.

For example:

  • EcoResProductMaster table extends EcoResProduct.
  • EcoResProduct contains a method displayProductName().
  • Since EcoResProductMaster is a child of EcoResProduct, displayProductName() can also be called on an instance of EcoResProductMaster.
  1. static void childTableEcoResProductMaster(Args _args)
  2. {
  3.     EcoResProductMaster productMaster;
  4.     EcoResProductName   productName;
  5.  
  6.     //EcoResProductMaster does not contain the method displayProductName, but it parent table does
  7.     productName = productMaster.displayProductName();
  8. }

[UPDATE: This is also true for methods like insert(), but not for find(), since find() is a static method. ]

The select statement is slower than a query
Retrieving data with a query is faster and more reliable that an X++ select statement. This surprised me because I thought it was the exact opposite.

The trainer said that AOT or X++ queries execute faster because they run a server side action and the data is never copied to the client. With a select statement the data makes several round trips to the database before the result of the select is available in the client.

Queries are supposedly also much easier to maintain than complex select statements.

[UPDATE: Test this yourself! To know if the query or select statement is faster use WinAPI::GetTickCount() before and after you retrieve the data. Get the difference in time at the start and end a few times and compare.]

..and other trivia:

  • CrossCompany select retrieves readonly data. To modify data across companies, use the changCompany() statement.
  • It is possible to retrieve data backwards with while select reverse
  • Tables contain a property called Table group, which provide a method for categorizing tables according to the type of data they contain.
  • Use query.prompt() on an AOT or X++ query to open a dialog and allow the user to modify the query.

I’ll be back with an update on day 2 tomorrow!

If you find any of the above topics interesting, confusing or incorrect, let me know in the comments and I will try to elaborate on them in a future post.

Finding the AUC files in a flash: Where to find the AppData folder

Whenever someone experiences a bizarre, inexplicable error, you will see a standard list of steps to try and solve the problem. These standard steps usually range from deleting the user setup on a form, to restarting the AOS.

If the unsolvable issue is limited to one user, a typical task to try is to delete the Application User Cache (AUC) files in the user’s AppData folder.

The AppData folder is hidden inside the user’s folder, and can be in different locations on different servers. I can get a little lost looking for the AppData folder… Luckily I have made a mind-blowing discovery this week: Windows have a thing called path variables or system variables. Some programmers use them to do amazing things, but the simplest way to use them is to type them in windows explorer.

     For example, on any windows server, open explorer and type: %Appdata%

    Hit enter.

    You will see the Application Data folder.

    Wow.

Back to AX, to the delete the AUC files:

  1. Close the user’s client.
  2. Open explorer, type %Appdata%.
  3. Delete the all files ending with .auc.
  4. Open the client.
  5. Celebrate your victory over the mysterious error! (or move on the the next desperate step on the list)

Additional tips:

  • If you frequently have to clear out the Appdata folder, get a batch script like this one.
  • Try the same thing for other system folders like %temp% or %programdata%.
  • Learn more about system variables here. Find out how you can set up your own and use them in scripts.

Hope this nifty trick saves you time and impresses your friends!