Who modified that method?

I was investigating an issue in one of our QA systems today trying to understand why it wasn’t behaving the same as the code in our development system. It didn’t take me too long to realise that someone had overridden our CUS layer code with some unexpected code in the USR layer and this was masking our CUS layer changes. I wanted to know who had made that change so that I could ask them “what the heck is that doing there?”.

Because this is a QA system, there’s no version control in place here to tell me who made the change – we simply don’t expect there to be any code changes to be made in this system, so I needed an alternative strategy. My first step was to look at the properties pane in the AOT but the ChangedBy and ChangedDate gave identical values to the CreatedBy and CreatedDate properties and I knew that this couldn’t be the actual modified date.

At this point I wondered whether the model store database might come to my rescue so I took a look in there. From previous work that I had done looking at the data model of the model store, I knew that I would need to look at the ModelElement table and maybe the ModelElementData table. Sure enough the ModelElementData table carries CreatedDateTime, ModifiedDateTime, CreatedBy and ModifiedBy fields so it looked like I was onto a solution.

I noticed that the ModelElement table carried a column called AxId which I guessed might be the AX element Id so I looked up the element Id of the object that I was interested in on the AOT property pane.

RetailTransactionTableX-AOT

You can see in the picture that on my system the table RetailTransactionTableX has ID 101212 and that it’s the static table method InsertToRegularTable that carries the USR layer modification that I’m interested in.

After a little trial and error I managed to work out a SQL query that would give me what I needed to know…

1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT p.[Name] AS Element
    ,c.[Name] AS ChildNode
    ,d.[MODIFIEDDATETIME]
    ,d.[MODIFIEDBY]
    ,d.[ModelId]
    ,m.[Name] AS modelName
  FROM [AX1QA_model].[dbo].[ModelElement] p
  JOIN [AX1QA_model].[dbo].[ModelElement] c ON c.ParentHandle = p.ElementHandle 
  JOIN [AX1QA_model].[dbo].[ModelElementData] d ON d.ElementHandle = c.ElementHandle
  JOIN [AX1QA_model].[dbo].[ModelManifest] m ON m.ModelId = d.ModelId
  WHERE p.AxId = 101212 
  AND c.Name = 'InsertToRegularTable'
  ORDER BY d.LayerId

As you can see I’m feeding the AX element Id 101212 and the name of the method that I’m interested in into the WHERE clause of my query and here’s the output that I got.

ModelWhoDunnit

So now I know that I need to go and see “edward.p” and find out what he was doing modifying code directly in my QA system.

UPDATE:

I should also say that this technique can just as easily be applied to any other AOT node, not just methods. So if you want to know who, for example, changed the properties of a field on a given table or who created a specific relation then you can follow the same principles.

“The Art of Debugging” – Tip 5 – Dynamics Anywhere Form Trace

The Dynamics Anywhere  mobile solution for AX is a great solution. As an aid to designing and developing new functionality and to aid the process of issue resolution I have developed a small tool that can be used to quickly identify the form trace for an active user. The tool displays the list of forms that the user has navigated through including their currently displayed form.

The tool works by interrogating the session table (Table: D2MFWApplicationSession) , the form stack table (Table: D2MFWFormInstanceStack) and the application instance table (Table: D2MFWApplicationInstanceTable).

This then produces output such as that shown by the following screenshot: –

 

image

In this example the user MJ has navigated through the form GRScanLocation, then onto form GCScanPurchId and is now at form GRListPurchId.

 

The following is a sample of the code used: –

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
static void DisplayDAWFormTrace(Args _args)
{
    D2MFWFormInstanceStack  D2MFWFormInstanceStack;
    D2MFWFormInstanceTable  D2MFWFormInstanceTable;
    D2MFWApplicationSession D2MFWApplicationSession;
    D2MFWApplicationInstanceTable D2MFWApplicationInstanceTable;
    Dialog                  dialog;
    DialogField             dlgUserId;
 
    D2MFWUserId             UserId;
 
    dialog = new Dialog(Select DAW User”);
 
    dlgUserId = dialog.addFieldValue(extendedTypeStr(D2MFWUserID),"");
 
    dialog.run();
 
    if (dialog.closedOk())
    {
        UserId = dlgUserId.value();
 
        setPrefix(strFmt("Form trace for user ‘%1’", UserId));
 
        select firstOnly * from D2MFWApplicationSession
        where D2MFWApplicationSession.userid == UserId;
 
        while select * from D2MFWFormInstanceStack
        order by LineNum
        join * from D2MFWApplicationInstanceTable
        where D2MFWApplicationInstanceTable.applicationInstanceID == D2MFWFormInstanceStack.applicationInstanceID &&
              D2MFWApplicationInstanceTable.sessionID             == D2MFWApplicationSession.sessionID
        {
            select firstonly * from D2MFWFormInstanceTable
            where D2MFWFormInstanceTable.formInstanceID        == D2MFWFormInstanceStack.formInstanceID &&
                  D2MFWFormInstanceTable.applicationInstanceID == D2MFWFormInstanceStack.applicationInstanceID;
 
            info(strFmt('%1', D2MFWFormInstanceTable.formConfigID));
 
        }
 
        select firstOnly * from D2MFWApplicationInstanceTable
        where D2MFWApplicationInstanceTable.sessionID             == D2MFWApplicationSession.sessionID;
 
        select firstonly * from D2MFWFormInstanceTable
        where D2MFWFormInstanceTable.formInstanceID          == D2MFWApplicationInstanceTable.currentformInstanceID &&
              D2MFWFormInstanceTable.applicationInstanceID   == D2MFWApplicationInstanceTable.applicationInstanceID;
 
        info(strFmt('%1', D2MFWFormInstanceTable.formConfigID));
 
    }
 
}

“The Art Of Debugging” – Tip 4 – Intercept a query

It has been a while since my last post on debugging. I haven’t run out of tips though – just been incredibly busy.

Sometimes when you are debugging the software in Ax you will be interested in seeing what records are being returned by a query. That can be easy when the records are displayed in a form and you can immediately see for yourself the returned data but when the query is running in X++ and not displayed directly on a screen it isn’t so easy to track.

One approach to make things easy is to override the PostLoad method at table level.

Take for example a list of Vendors. If we override the method PostLoad on table VendTable we can then set a breakpoint on this method. The breakpoint will be hit for each VendTable record returned by any query on that table. This applies to records returned by a form data source, by a query object or even by a direct X++ select statement.

This technique can prove to be a very good way to trace all of the reads on a particular table but don’t leave the method behind when you’re finished debugging as it will negatively impact performance of reads on that table.

Keep on debugging!