Dynamics AX 2012: Find out who changed the code

Standard

If you will have multiple people working in an AX environment who are modifying/customizing code as well you may have a need to find out who actually modified the code that was in place so you may get in touch with them.

Following SQL script will be helpful. If you execute the script against the model database it will get you the information that is  not available within the AOT.


select ME.ElementHandle, ME.Name, L.Name, dateadd(HOUR,DATEDIFF(HOUR, GETUTCDATE(), GETDATE()),MED.CREATEDDATETIME) as createddatetime, MED.CREATEDBY, dateadd(HOUR,DATEDIFF(HOUR, GETUTCDATE(), GETDATE()),MED.MODIFIEDDATETIME) as ModifiedDateTime, MED.MODIFIEDBY from ModelElement ME
inner join ModelElementData MED on ME.ElementHandle = MED.ElementHandle
inner join Layer L on L.Id = MED.LayerId
where ME.name like '%getLastProcessingTimeForSpearDocument%'


In the above script getLastProcessingTimeForSpearDocument is the method name for which I needed to find out who modified it last or in another layer.

Sample out looks like this:

output

AX 2012: Enterprise Portal – Double Asterisk in AXBoundField Property DataFeild Value

Standard

Recently when I was working on the modification of an existing EP page I came across a AXGridView control which has some of its column bound to DataField which has double asterisk (‘**’) appended at the end of the value name, something like following:

<dynamics:AxGridView id="gridLines" runat="server"
                            ShowContextMenu="false" AutoGenerateColumns="False" UpdateOnPostBack="true"
                            DataSourceID="PurchReqWizardDS" DataMember="PurchReqLine" DataKeyNames="RecId"
                            AllowPaging="True" ShowFilter="false" AllowEdit="true" AllowDelete="true" AllowInsert="false" Enabled="true">
<Columns>
<dynamics:AxBoundField DataSet="PurchReqWizard" DataSetView="PurchReqLine" DataField="vendName**" ReadOnly="true" HeaderText="<%$ axlabel:@SYS130745 %>" />
</Columns>
</dynamics:AxGridView>

Now this was all in the asp.net page, even though I have had significant experience working in asp.net previously but I have never seen similar code before, which made me think that this is something done to EP modification in shape of wrappers on top of ASP.net code. I tried to look for the explanation in Enterprise Portal Development Cookbook, though I could see the usage of it in the cookbook but no explanation was provided. I decided to debug the code uncover the binding, first thing I found that the value name in the above example vendName wasn’t a column on the table, upon deeper investigation I found out that vendName was a display method defined on the PurchReqLine table:

display PurchReqVendName vendName()
{
    if (this.VendAccount == "")
    {
        return "";
    }
    else
    {
        changecompany(this.buyingLegalEntity2DataArea())
        {
            return VendTable::find(this.VendAccount).name();
        }
    }
}

This whole exercise made me realize that in EP if you have to bound a grid column to a calculate value as in terms of a edit method, then you would add two asterisk to the name of the method in the AXBoundField.

I hope this helps someone.

AX 2012: BatchHeader Object not initialized error

Standard

If you are visiting this blog page then you might be experiencing this mysterious error which comes out of nowhere. Good news is that there is a simple solution to it.

If you are having BtachHeader Object not initialized error just like I was when I was trying to run the upgrade checklist for one of our customer installation:

Image

You will also be shown a stack trace and the last class where the exception is raised is very important as you will need its ID. So as you may be able to see the the last class in the stack trace is SysUserLicenseMiner and its ID is 1019821

Image

Once you have the ID, run a select query in the SQL management studio for the database to which is connected on the batch table as following:

select * from BATCH where CLASSNUMBER = 1019821

For the classnumber column you will be using your own identified Class ID. This should return you at least one record or it can be more than one record.

Next step would be to delete this record, because this error is being thrown due to some leftover data and must be cleansed before you can make any further progress.

delete from BATCH where CLASSNUMBER = 1019821

Again for the classnumber column you will be using your own identified Class ID.

Once I deleted the record, I didn’t have to face that error again. I hope this works for you as well.

Ax 2012: Customizing TFS chekin work items list

Standard

If your are using Team Foundation Server with AX 2012 Development environment one nice feature which comes out of the box is to make check-ins against or associating it with TFS work item assigned to you. Like following:

Image

But after a while when you have fixed few bugs/tasks, those done items keep appearing. In my case the list kept growing to such an extent that it started bothering me and made it difficult for me to find out which work item to check the code against. I decided to figure out how this data is coming out of TFS and being displayed.

I found that AX 2012 is using select based query for getting data out of TFS. You can find more details on how to construct a select based query here.

Code that you will have to modify to change the query to your need is as following:

Following Class\Method is where you have to change the code.

\Classes\SysVersionControlWorkItemProviderTFS\getItemsAssignedToMe

/// <summary>
/// Gets the work items that are assigned to the current user.
/// </summary>
/// <returns>
/// A number of records in the <c>SysVersionControlTmpWorkItems</c> table.
/// </returns>
public SysVersionControlTmpWorkItems getItemsAssignedToMe()
{
 SysVersionControlTmpWorkItems ret;
 Microsoft.Dynamics.AX.Framework.TeamFoundationServerFacade.WorkItemProxyCollection workItems;
 Microsoft.Dynamics.AX.Framework.TeamFoundationServerFacade.WorkItemProxy workItem;
 int i, itemsCount;
 str assignedToMe = "SELECT [System.Id], [System.Title], [System.State], [System].[Work Item Type] FROM WorkItems WHERE [System.TeamProject] = @project AND [System.AssignedTo] = @me AND System.State <> 'Closed' AND TYLER.WorkUnit.State <> 'Completed' AND Bug.Tyler.Status <> 'Closed' AND Tyler.Request.Status <> 'Closed' ORDER BY [System.Id]";
 workItems = workItemStoreProxy.ExecuteWorkItemQuery(assignedToMe);
 itemsCount = workItems.get_Count();

for (i = 0; i < itemsCount; i++)
 {
 workItem = workItems.get_Item(i);
 this.insertWorkItemToTable(ret, workItem);
 }

return ret;
}

In the above method code, highlighted lines is where the action is.

I hope this will be helpful for someone in future.

AX 2012: Failed to restart AOS service

Standard

If you start/restart the AOS service and the service takes a little time to start and then just dies, then go and check the event viewer node Windows Logs  à Application and try to find last error entries within the log. If you encounter an error where it complains that the user doesn’t have enough rights something like following which I had in my case

Object Server 01 : An error situation occurred during synchronization of label files in the model store and the local label files.

 The error reported is: System.Data.SqlClient.SqlException (0x80131904): Cannot open database “CoR_model” requested by the login. The login failed.

Login failed for user ‘NT AUTHORITY\NETWORK SERVICE’.

Then open the SQL Server Management studio and connect to the server where your AX DBs are. Locate the user with which you are running the AOS service within the Security à Logins node. In my case it was NT Authority\Network Service. Double click the user to open the Login Properties form, and select the User Mapping tab. Ensure that all the AX DBs are mapped to the user, if not then map it. In my case CoR_model was not mapped (also watch closely the setting in the lower pane should match).

Image

Also try

USE [CoR_model]
GO
GRANT EXECUTE TO [NT AUTHORITY\NETWORK SERVICE]
GO

After mapping restart the AOS service and hopefully you will get it started or atleast get past this rights error.

Dynamics AX 2012: Executing Trace Cockpit through Code

Standard

A lot of the time a situation arises where you need to run the trace cockpit for debugging purpose and you have pinpointed code for which you want to run the trace cockpit. But AX doesn’t expose any easy API to do it via code. I have figured out a way to do it via code, in my example I wanted to check what SQL statements were hitting DB when I was modifying a control on the form.

public void modified()
{
 #WinAPI
 #File
SysTraceCockpitController sysTraceCockpitController;
 Filename clientFileName = "C:\\XPO\\JP1.ETL";
 boolean captureServerTrace = true, useCircularLogging = false;
 int64 keywords = 255; //The 255 value enables all the options available.
 int maxFileSize = 1000, traceStatus, traceStatusServer; 

 sysTraceCockpitController = SysTraceCockpitController::construct(clientFileName,
 captureServerTrace,
 null,
 keywords,
 maxFileSize,
 useCircularLogging);
 [ traceStatus,
 traceStatusServer ] = sysTraceCockpitController.startTracing();

 if (captureServerTrace &amp;amp;amp;amp;&amp;amp;amp;amp;
 traceStatusServer != #ERROR_SUCCESS &amp;amp;amp;amp;&amp;amp;amp;amp;
 traceStatusServer != #AXTRACE_STATUS_ALREADYEXISTS)
 {
      error("@SYS340542");
      traceStatus = traceStatusServer;
 }

 if (traceStatus != #ERROR_SUCCESS)
 {
      throw error (WinAPI::formatMessage(traceStatus));
 }

 super(); //the whole code for trace cockpit execution is wrapped around this line.

 sysTraceCockpitController.stopTracing(false);

}

I hope this will help someone in near future.

Dynamics AX 2012: How to randomly select values from a list

Standard

Today I was trying to build some data for tables by using some existing data. After a while doing that proving to be boring I decided to make it interesting by writing a method which when called would only get me randomly selected data within the list of items. After searching the internet and realizing the limited capability of AX 2012 Random class, I had to write my own routine for this. Sharing it with y’all.


private static Set selectedListRandomizer(Map _itemsToSelectFrom, int _numberOfItemsToSelect)
 {
 Set selected = new Set(Types::Int64);
 int needed = _numberOfItemsToSelect;
 int available;
 Random rand = new Random();

while (selected.elements() < _numberOfItemsToSelect)
 {
 if (available == 0)
 {
 available = _itemsToSelectFrom.elements();
 }

if( rand.nextInt() < needed / available )
 {
 if (!(selected.in(_itemsToSelectFrom.lookup(available))))
 {
 selected.add(_itemsToSelectFrom.lookup(available));
 needed--;
 }
 }
 available--;
 }

return selected;
 }

The first parameter of the method accepts a Maps object which would containt all the items from which random select the number of items identified in the second parameter. For example following code shows its usage, where 2 items need to be picked up from a list of workers which are teacher type:


HcmWorker hcmWorker;
 Map items = new Map(Types::Integer, Types::Int64);
 Set randomItemsSet;
 int counter = 0;

while select RecId from hcmWorker
 where hcmWorker.IsTeacher == true
 {
 counter++;
 items.insert(counter, hcmWorker.RecId);
 }

randomItemsSet = DataPopulate::selectedListRandomizer(items, 2);

I hope this will help someone to get going and benefit from this code piece.