How to open AX with a User Role and debug it at the same time in Dynamics #AX2012

Recently came across a scenario where we had to debug a user session that had limited rights to the system, one way of doing that could be for instance walking through a trace of the user session and another would be to debug code while user steps through the process but that would be done only for server side calls. So what if we need to debug client side calls as well, how to go about doing that was the challenge. Since if we assign the user role to an admin and execute the process we also have admin privileges which are higher up the ladder and do not limit access to what that user role has. Not to worry there is a way you can do this by following the steps below:

1. Run Dynamics AX as an administrator
2. Add the role you want to debug to your own user, let the Sys Admin role stay as well.
3. Open a new development Work space.
4. Place breakpoints where you need them.
5. Create below job
static void NonAdmin(Args _args)
{
securityutil::sysadminmode(false);
}
7. Execute the above job
8. Open a new workspace
9. Run the process you wanted to debug.

You should now be able to hit the break point you placed in Microsoft Dynamics AX 2012 Debugger as the User role.

P.s I found this thanks to an old post by a colleague of mine.

 

How to Read a Trace log etl file in Dynamics AX 2012

In my previous post I showed how to capture a Trace of Payment Term creation that results in an etl file being generated. To read that trace we need to have Microsoft Dynamics AX Trace Parser installed. You do not need to be on the same machine that you took the trace on, you only need the ETL file. In the below video I quickly go over how to read a Trace file and the different options Trace parser provides. To see how to capture a trace read: How to capture a Trace log of any process in Dynamics AX 2012

How to capture a Trace log of any process in Dynamics AX 2012

Often times debugging a process is not enough to identify a bug or a query that is taking too long or the scenario only happens on a specific machine be it production or test and we need to get to the bottom of what is wrong that causes the issue. This calls for tracing the process and stepping through each cycle as it happened, thanks to Trace parser and the tracing cockpit provided in Dynamics AX 2012 we can capture every query executed and method called during that process. In the below video I show how to capture a trace log of any process.

 

Solution: The Table.Field does not have a corresponding parm-method on the AxBC-class

If you have added new fields to an existing table and receive the below best practice error, it means you need to create a parm method. This can be automatically done with a simple one line code.

For example If we add the field below

We’ll receive the following error:

“The Table.Field does not have a corresponding parm-method on the AxBC-class”

Solution:

Create a job and execute this code below

static void Job8(Args _args)
{
AxGenerateAxBCClass::newTableId(tableNum(PurchTable)).run();
}

This creates the parm method which will not only get rid of the BP error but now you have your new field synced with the table creation field when an AIF service, a data migration entity or initialization is done.

Things to do while Restoring a production backup on a test or a Dev box in #AX2012

Once your db restore is done. Before starting services make sure you do the following:

Stop any batch jobs to avoid posting or pushing data to systems configured in production. This can be done by below SQL

UPDATE [DynamicsAXDev].[dbo].[BATCHJOB] SET [STATUS] = 0

WHERE [STATUS] IN (1,2,5) 

 

An Admin User would already exist, Use below query to set your user as Admin:

UPDATE USERINFO
SET Name = ‘Mohsin’,
SID = ‘S-1-5-21-542508066-1570976776-XXXXXXX-54688’,
NetworkDomain = ‘XXXX.XX’,
NetworkAlias = ‘XXXX’
WHERE ID = ‘Admin’

Start services and replace the settings described in the video below to set your batch servers.

Address and contact information table relationships to a party in #AX2012 & #Dyn365FO

Both address and contact information on a customer, vendor or any party record is stored at the party level. So while the parties Customer and Vendor are company specific the party record is cross company. If you create a customer against the same party in two companies and modify the address of Customer A in company X you will see the changes for Customer B in company Y. This is because when we are modifying a customers address or contact, it is actually modifying the party address and contact. You can test this out without getting into the development environment by creating a customer in one company with an address and contact, note down its party number that was created, then create a customer in another company and use the change party association option to assign the newly created customer the party number of the previously created customer. After this, change the address on any one of the customers and you will see the changes reflect on the other company customer as well. Now generally this is not the case as when you create a customer in a company a new party record gets created against it and within AX you always create a customer or vendor first and not a party itself.

Coming down to the technical’s in play for this. A party record is stored in the DirPartyTable and in order to query its locations(addresses/contact information) you can query the DirPartyLocation table which will give out  records that each represent an address and one record that represents all contact information records, to identify these there are NoYes enum fields such as IsPostalAddress. For example if there are 3 addresses on a customer and 5 contact information records, this table will show you 4 records. Each record contains a Location field that represents the RecId of  the LogicticsLocation table, this table contains links to the address and contact information records. Addresses are saved in LogisticsPostalAddress table and contact information is stored in LogisticsElectronicAddress table, each table has as relationship with the LogisticsLocation table as shown below.

Contains 4 records, 3 for addresses and one for contacts:

These records can be used to query the address in LogisticsPostalAddress table below, wrt to the following relationship:

Hopefully this gives you an idea of the crud operations of an address and contact information in AX. This applies to both AX 2012 and D365.

Shrinking a Database Log file through SQL

Often our DB logs grow out of hand and we are left with limited space; a common occurrence on dev boxes for AX. So among other remedies to get more space is shrink your database log files and you can do so by using the queries below. Make sure you back up your databases before you run this, make this a standard practice before changing/modifying any meta on your db servers. You can do this for both your trans and model databases for Dynamics AX 2012.

USE DynamicsAXDev_model;

GO

— Truncate the log by changing the database recovery model to SIMPLE.

ALTER DATABASE DynamicsAXDev_model

SET RECOVERY SIMPLE;

GO

— Shrink the truncated log file to 1 MB.

DBCC SHRINKFILE (DynamicsAXDev_model_Log, 1);

GO

— Reset the database recovery model.

ALTER DATABASE DynamicsAXDev_model

SET RECOVERY FULL;

GO

AX Tables updated when a Pending Vendor Invoice is Posted inside #AX2012

An invoice when posted goes into the following tables:

For Invoice Header:

VendInvoiceJour defaults from VendInvoiceInfoTable

For Invoice lines:

VendInvoiceTrans defaults are from VendInvoiceInfoline table

See bttom to top:


Then a single record is inserted in VendTrans then VendTransOpen, see bottom to top

Change or Remove a dimension value on an Item through X++ in AX 2012

Using the below job you can update dimension values on an item.

</pre>

static void UpdateDepartmentMISC_Items(Args _args)
{

InventTable inventTable;
DimensionAttributeValueSetStorage dimStorage;
DimensionAttribute dimAttDept, dimAttMisc;
DimensionAttributeValue dimAttributeValueDept, dimAttributeValueMisc;

DataAreaId company;
str misc, dept;
ItemId itemId;
int counter = 0;

#define.DepartmentDimensionToUpdate('Department')
#define.MISCDimensionToUpdate('MISC')

info(strFmt('Start time: %1', time2StrHM(timeNow())));

changeCompany(company)

{

inventTable = null;

dimStorage = null;

dimAttMisc = null;

dimAttDept = null;

dimAttributeValueDept = null;

dimAttributeValueMisc = null;

while select forUpdate inventTable

where inventTable.ItemId == itemId

&& inventTable.dataAreaId == curext()

{

counter++;

try

{

ttsBegin;

dimStorage = DimensionAttributeValueSetStorage::find(inventTable.DefaultDimension);

dimAttDept = DimensionAttribute::findByName(#DepartmentDimensionToUpdate);

dimAttMisc = DimensionAttribute::findByName(#MISCDimensionToUpdate);

dimAttributeValueDept = DimensionAttributeValue::findByDimensionAttributeAndValue(dimAttDept, dept, true, true);

&nbsp;

if (misc)

{

dimAttributeValueMisc = DimensionAttributeValue::findByDimensionAttributeAndValue(dimAttMisc, misc, true, true);

dimStorage.addItem(dimAttributeValueMisc);

}

&nbsp;

dimStorage.removeDimensionAttributeValue(dimAttributeValueDept.RecId);

inventTable.DefaultDimension = dimStorage.save();

inventTable.doUpdate();

ttsCommit;

}

catch(Exception::Error)

{

error(strFmt("Error to update dimension for item %1", inventTable.itemid));

}

}

}

&nbsp;

info(strFmt("Records updated: %1", Counter));

info(strFmt('End time; %1', time2StrHMS(timeNow())));

}
<pre>