How to use Dynamics Perf 2.0 to monitor and improve the AX 2012 Performance



Please find the List of Queries to monitor and improve the performance of AX from Dyanmics Perf 2.0.

Once you complete  the installation of Dynamics Perf  2.0  from the below link.


It will start collecting the data and store it in the DynamicsPerf database.  

To identify the below list , please query the DynamicsPerf Database name and not AX database as shown below.


  • Ø  INDEXES_BY_SIZE
  • Ø  INDEX_ACTIVITY
  • Ø  INDEX_STATISTICS
  • Ø  TOO_LARGE_INDEXES
  • Ø  HIGH_COST_INDEXES
  • Ø  COMPRESSED_INDEXES
  • Ø  UNIQUE_INDEXES_NOT_DEFINED_UNIQUE
  • Ø  EXACT_DUPLICATE_INDEXES
  • Ø  SUBSET_DUPLICATE_INDEXES
  • Ø  INCLUDED_COLUMN_INDEXES
  • Ø  UNUSED_INDEXES
  • Ø  TABLES_WITHOUT_CLUSTERED_INDEX
  • Ø  ADJUST_CLUSTERED_INDEXES
  • Ø  ANALYZE_INDEX_KEY_ORDER
  • Ø  INDEXES_BEING_SCANNED
  • Ø  SEARCH_QUERY_PLANS_FOR_INDEX_USAGE





To Find The Index by size

USE DynamicsPerf
SELECT TOP 100 DATABASE_NAME,
TABLE_NAME,
SUM(CASE
WHEN ISV.INDEX_ID IN (0,1)  THEN PAGE_COUNT * 8 / 1024
END)   AS SIZEMB_DATA,
SUM(CASE
WHEN ISV.INDEX_ID > 1 THEN PAGE_COUNT * 8 / 1024
END)   AS SIZEMB_INDEXES,
COUNT(CASE
WHEN ISV.INDEX_ID > 1 THEN TABLE_NAME
END) AS NO_OF_INDEXES,
MAX(CASE
WHEN ( DATA_COMPRESSION > 0 )
AND ( ISV.INDEX_ID IN (0,1)  ) THEN 'Y'
ELSE 'N'
END)   AS DATA_COMPRESSED,
MAX(CASE
WHEN ( DATA_COMPRESSION > 0 )
AND ( ISV.INDEX_ID > 1) THEN 'Y'
ELSE 'N'
END)   AS INDEXES_COMPRESSED
FROM   INDEX_STATS_CURR_VW ISV
--WHERE DATABASE_NAME = 'Your AX Database Name'
--AND SERVER_NAME = 'Database Server Name'
GROUP  BY SERVER_NAME,
DATABASE_NAME,
TABLE_NAME
ORDER  BY 3 DESC
___________________________________________________________________________________

Index Activity

SELECT DATABASE_NAME,
TABLE_NAME,
CASE
WHEN ( SUM(USER_UPDATES + USER_SEEKS + USER_SCANS
+ USER_LOOKUPS) = 0 ) THEN NULL
ELSE ( CAST(SUM(USER_SEEKS + USER_SCANS + USER_LOOKUPS) AS DECIMAL) / CAST(SUM(USER_UPDATES + USER_SEEKS + USER_SCANS + USER_LOOKUPS) AS DECIMAL) ) END   AS ratioofreads,
CASE
WHEN ( SUM(USER_UPDATES + USER_SEEKS + USER_SCANS
+ USER_LOOKUPS) = 0 ) THEN NULL
ELSE ( CAST(SUM(USER_UPDATES) AS DECIMAL) / CAST(SUM(USER_UPDATES + USER_SEEKS + USER_SCANS + USER_LOOKUPS) AS DECIMAL) ) END       AS ratioofwrites,
SUM(USER_SEEKS + USER_SCANS + USER_LOOKUPS) AS totalreadoperations,
SUM(USER_UPDATES)                           AS totalwriteoperations,
SUM(USER_UPDATES + USER_SEEKS + USER_SCANS
+ USER_LOOKUPS)                         AS totaloperations
FROM   INDEX_STATS_CURR_VW /*sys.dm_db_index_usage_stats*/
--WHERE DATABASE_NAME = 'XXXXXXXXXXXX'
-- AND SERVER_NAME = 'XXXXXXXX'
GROUP  BY SERVER_NAME,
DATABASE_NAME,
TABLE_NAME
--ORDER BY TotalOperations DESC
--ORDER BY TotalReadOperations DESC
ORDER  BY TotalWriteOperations DESC
_____________________________________________________________________________________


INDEX_STATISTICS

SELECT DATABASE_NAME,
SERVER_NAME,
TABLENAME,
INDEXNAME,
UPDATED
FROM   INDEX_STAT_HEADER ISH
WHERE  UPDATED IS NOT NULL
-- AND SERVER_NAME = 'XXXXXXXXX'
-- AND DATABASE_NAME = 'XXXXXXXX'
ORDER  BY UPDATED DESC
___________________________________________________________________________________________

TOO_LARGE_INDEXES 

;WITH TABLE_SIZE (SERVER_NAME, DATABASE_NAME, TABLE_NAME, SIZE)
AS (SELECT SERVER_NAME,DATABASE_NAME,
TABLE_NAME,
PAGE_COUNT * 8 / 1024 AS size
FROM   INDEX_STATS_CURR_VW
WHERE  INDEX_ID IN (0,1) )
SELECT DISTINCT ISV.DATABASE_NAME,
ISV.TABLE_NAME,
TS.SIZE,
ISV.INDEX_NAME,
( ( PAGE_COUNT * 8 / 1024 ) / TS.SIZE ) * 100 AS [%_of_table],
ISV.INDEX_DESCRIPTION,
ISV.INDEX_KEYS,
ISV.INCLUDED_COLUMNS
FROM   INDEX_STATS_CURR_VW ISV
INNER JOIN TABLE_SIZE TS
ON ISV.SERVER_NAME = TS.SERVER_NAME
AND ISV.DATABASE_NAME = TS.DATABASE_NAME
AND ISV.TABLE_NAME = TS.TABLE_NAME
AND TS.SIZE > 0
WHERE  ISV.INDEX_ID > 1
AND USER_UPDATES > 100 -- index must be getting update
AND PAGE_COUNT > 100 -- small parameter tables are ok
AND ( ( PAGE_COUNT * 8 / 1024 ) / TS.SIZE ) * 100 > 25 --25%
ORDER  BY TS.SIZE DESC

__________________________________________________________________________________________


 HIGH_COST_INDEXES

SELECT SERVER_NAME,
DATABASE_NAME,
TABLE_NAME,
INDEX_NAME,
PAGE_COUNT*8/1024 AS SIZE_MB,
CASE
WHEN ( SUM(USER_UPDATES + USER_SEEKS + USER_SCANS
+ USER_LOOKUPS) = 0 ) THEN NULL
ELSE ( CAST(SUM(USER_SEEKS + USER_SCANS + USER_LOOKUPS) AS DECIMAL) / CAST(SUM(USER_UPDATES + USER_SEEKS + USER_SCANS + USER_LOOKUPS) AS DECIMAL) ) END  AS RatioOfReads,
CASE
WHEN ( SUM(USER_UPDATES + USER_SEEKS + USER_SCANS
+ USER_LOOKUPS) = 0 ) THEN NULL
ELSE ( CAST(SUM(USER_UPDATES) AS DECIMAL) / CAST(SUM(USER_UPDATES + USER_SEEKS + USER_SCANS + USER_LOOKUPS) AS DECIMAL) ) END       AS RatioOfWrites,
SUM(USER_SEEKS + USER_SCANS + USER_LOOKUPS) AS TotalReadOperations,
SUM(USER_UPDATES)                           AS TotalWriteOperations,
SUM(USER_UPDATES + USER_SEEKS + USER_SCANS
+ USER_LOOKUPS)                         AS TotalOperations
FROM   INDEX_STATS_CURR_VW /*sys.dm_db_index_usage_stats*/
WHERE   USER_UPDATES > (USER_SEEKS + USER_SCANS + USER_LOOKUPS)
AND INDEX_DESCRIPTION NOT LIKE '%UNIQUE%' and INDEX_DESCRIPTION NOT LIKE 'CLUSTERED%'
-- AND SERVER_NAME = ‘XXXXXXXXX’
-- AND DATABASE_NAME = ‘XXXXXXXX’
GROUP  BY SERVER_NAME,
DATABASE_NAME,
TABLE_NAME,
INDEX_NAME,
PAGE_COUNT
--ORDER BY TotalOperations DESC
--ORDER BY TotalReadOperations DESC
ORDER  BY TotalWriteOperations DESC

_____________________________________________________________________________________________


COMPRESSED_INDEXES

SELECT *
FROM   INDEX_STATS_CURR_VW
WHERE  DATA_COMPRESSION > 0
ORDER  BY USER_UPDATES DESC

________________________________________________________________________________________


 UNIQUE_INDEXES_NOT_DEFINED_UNIQUE

;WITH RECORDS_CTE (SERVER_NAME, DATABASE_NAME, TABLENAME, INDEX_NAME, INDEX_KEYS, ROW_COUNT, READS)
AS
-- Define the CTE query.
(SELECT DISTINCT SERVER_NAME,
DATABASE_NAME,
TABLE_NAME,
INDEX_NAME,
INDEX_KEYS,
ROW_COUNT,
USER_SEEKS + USER_SCANS + USER_LOOKUPS AS reads
FROM   INDEX_STATS_CURR_VW)
SELECT DISTINCT dv.SERVER_NAME,
dv.DATABASE_NAME,
dv.TABLENAME,
rows.INDEX_NAME,
DS.TYPE_DESC AS index_desc,
rows.INDEX_KEYS,
rows.ROW_COUNT
FROM   INDEX_DENSITY_VECTOR dv
INNER JOIN RECORDS_CTE rows
ON dv.TABLENAME = rows.TABLENAME
AND dv.DATABASE_NAME = rows.DATABASE_NAME
AND dv.SERVER_NAME = rows.SERVER_NAME
INNER JOIN DYNSYSINDEXES DS
ON DS.DATABASE_NAME = dv.DATABASE_NAME
AND DS.RUN_NAME LIKE dv.SERVER_NAME + '%'
AND DS.NAME = rows.INDEX_NAME
WHERE  DS.IS_UNIQUE = 0
AND ROW_COUNT / ( 1 / DENSITY ) = 1.000000000000000000
AND rows.READS > 1000 -- relatively used tables
AND rows.ROW_COUNT > 10000 -- larger tables only
ORDER  BY ROW_COUNT DESC

__________________________________________________________________________________________

EXACT_DUPLICATE_INDEXES

SELECT SERVER_NAME,
DATABASE_NAME,
TABLE_NAME,
INDEX_KEYS,
COUNT(*),
STUFF ((SELECT ', ' + 'INDEX_NAME = ' + ISV.INDEX_NAME
+ ' KEYS= ' + ISV.INDEX_KEYS
+ ' INCLUDED_COLUMNS= '
+ ISV.INCLUDED_COLUMNS
FROM   INDEX_STATS_CURR_VW ISV
WHERE  ISV.DATABASE_NAME = A.DATABASE_NAME
AND ISV.SERVER_NAME = A.SERVER_NAME
AND ISV.TABLE_NAME = A.TABLE_NAME
AND ISV.INDEX_KEYS = A.INDEX_KEYS
FOR xml path('')), 1, 1, '''') AS indexes
FROM   INDEX_STATS_CURR_VW A
WHERE  INDEX_DESCRIPTION NOT LIKE '%primary key%'
GROUP  BY SERVER_NAME,
DATABASE_NAME,
TABLE_NAME,
INDEX_KEYS
HAVING COUNT(INDEX_KEYS) > 1
ORDER  BY SERVER_NAME,
DATABASE_NAME,
TABLE_NAME

__________________________________________________________________________________________________


SUBSET_DUPLICATE_INDEXES

SELECT DISTINCT O.SERVER_NAME,
O.DATABASE_NAME,
O.TABLE_NAME,
O.INDEX_NAME            AS subset_index,
O.INDEX_KEYS            AS subset_index_keys,
O.INDEX_DESCRIPTION     AS subset_index_description,
O.PAGE_COUNT * 8 / 1024 AS subset_size_mb,
I.INDEX_NAME            AS superset_index,
I.INDEX_KEYS            AS superset_keys
FROM   INDEX_STATS_CURR_VW O
LEFT JOIN INDEX_STATS_CURR_VW I
ON I.SERVER_NAME = O.SERVER_NAME
AND I.DATABASE_NAME = O.DATABASE_NAME
AND I.TABLE_NAME = O.TABLE_NAME
AND I.INDEX_KEYS <> O.INDEX_KEYS
AND I.INDEX_KEYS LIKE O.INDEX_KEYS + ',%'
WHERE  O.INDEX_DESCRIPTION NOT LIKE '%UNIQUE%'
AND O.INDEX_DESCRIPTION NOT LIKE 'CLUSTERED%'
AND I.INDEX_NAME IS NOT NULL
AND O.PAGE_COUNT > 0
ORDER  BY O.SERVER_NAME,
O.DATABASE_NAME,
O.TABLE_NAME,
O.INDEX_KEYS

____________________________________________________________________________________________

INCLUDED_COLUMN_INDEXES

SELECT TOP 100 *
FROM   INDEX_STATS_CURR_VW
WHERE  INCLUDED_COLUMNS <> 'N/A'
AND PAGE_COUNT > 0
ORDER  BY len(INCLUDED_COLUMNS) DESC

_____________________________________________________________________________________________________


UNUSED_INDEXES

SELECT SERVER_NAME,
DATABASE_NAME,
TABLE_NAME,
INDEX_NAME,
MAX(PAGE_COUNT * 8 / 1024)         AS SIZE_MB,
SUM(USER_SEEKS_DELTA + USER_SCANS_DELTA
+ USER_LOOKUPS_DELTA)          AS READ_COUNT,
MIN(DATEDIFF(DD, DATE, GETDATE())) AS DAYS
FROM   INDEX_HISTORY
WHERE  FLAG = 'M'
AND INDEX_DESCRIPTION NOT LIKE '%UNIQUE%'
AND INDEX_DESCRIPTION NOT LIKE 'CLUSTERED%'
AND INDEX_DESCRIPTION NOT LIKE '%FILTERED%'
GROUP  BY SERVER_NAME,
DATABASE_NAME,
TABLE_NAME,
INDEX_NAME
HAVING SUM(USER_SEEKS_DELTA + USER_SCANS_DELTA
+ USER_LOOKUPS_DELTA) = 0
AND MAX(PAGE_COUNT * 8 / 1024) > 0
ORDER  BY 5 DESC
--Indexes not used this month but used last month
SELECT TMH.*
FROM   INDEX_HISTORY TMH
INNER JOIN INDEX_HISTORY LMH
ON TMH.SERVER_NAME = LMH.SERVER_NAME
AND TMH.DATABASE_NAME = LMH.DATABASE_NAME
AND TMH.TABLE_NAME = LMH.TABLE_NAME
AND TMH.INDEX_NAME = LMH.INDEX_NAME
AND TMH.DATE = Dateadd(MONTH, 1, LMH.DATE)
WHERE  TMH.FLAG = 'M'
AND LMH.FLAG = 'M'
AND TMH.INDEX_DESCRIPTION NOT LIKE '%UNIQUE%'
AND TMH.INDEX_DESCRIPTION NOT LIKE 'CLUSTERED%'
AND ( TMH.USER_SEEKS_DELTA
+ TMH.USER_LOOKUPS_DELTA
+ TMH.USER_SCANS_DELTA ) = 0 --REH Not used this month
AND ( LMH.USER_SEEKS_DELTA
+ LMH.USER_LOOKUPS_DELTA
+ LMH.USER_SCANS_DELTA ) > 0 --REH was used last month

 ______________________________________________________________________________________

TABLES_WITHOUT_CLUSTERED_INDEX

SELECT CLUS.SERVER_NAME,
CLUS.DATABASE_NAME,
CLUS.TABLE_NAME,
CLUS.INDEX_NAME                                      AS heap_table,
CLUS.INDEX_KEYS                                      AS clustered_keys,
NONCLUS.INDEX_NAME                                   AS nonclustered_index,
NONCLUS.INDEX_KEYS,
( NONCLUS.RANGE_SCAN_COUNT - CLUS.RANGE_SCAN_COUNT ) AS nonclustered_vs_clustered_range_count,
CLUS.USER_SEEKS                                      AS clustered_user_seeks,
CLUS.USER_SCANS                                      AS clustered_user_scans,
CLUS.SINGLETON_LOOKUP_COUNT                          AS clustered_single_lookups,
CLUS.RANGE_SCAN_COUNT                                AS clustered_range_scan,
NONCLUS.USER_SEEKS                                   AS nonclustered_user_seeks,
NONCLUS.USER_SCANS                                   AS nonclustered_user_scans,
NONCLUS.SINGLETON_LOOKUP_COUNT                       AS nonclustered_single_lookups,
NONCLUS.RANGE_SCAN_COUNT                             AS nonclustered_range_scans,
NONCLUS.USER_UPDATES                                 AS nonclustered_user_updates
FROM   INDEX_STATS_CURR_VW CLUS
INNER JOIN INDEX_STATS_CURR_VW NONCLUS
ON CLUS.TABLE_NAME = NONCLUS.TABLE_NAME
AND CLUS.DATABASE_NAME = NONCLUS.DATABASE_NAME
AND CLUS.SERVER_NAME = NONCLUS.SERVER_NAME
AND CLUS.INDEX_NAME <> NONCLUS.INDEX_NAME
WHERE  CLUS.INDEX_DESCRIPTION LIKE 'HEAP%'
AND ( ( NONCLUS.RANGE_SCAN_COUNT > CLUS.RANGE_SCAN_COUNT )
OR ( NONCLUS.SINGLETON_LOOKUP_COUNT > CLUS.SINGLETON_LOOKUP_COUNT ) )
AND CLUS.PAGE_COUNT > 0
ORDER  BY CLUS.USER_LOOKUPS DESC,
CLUS.TABLE_NAME,
( NONCLUS.RANGE_SCAN_COUNT - CLUS.RANGE_SCAN_COUNT ) DESC

_____________________________________________________________________________________________

ADJUST_CLUSTERED_INDEXES

SELECT CLUS.SERVER_NAME,
CLUS.DATABASE_NAME,
CLUS.TABLE_NAME,
CLUS.INDEX_NAME                                      AS CLUSTERED_INDEX,
CLUS.INDEX_KEYS                                      AS CLUSTERED_KEYS,
NONCLUS.INDEX_NAME                                   AS NONCLUSTERED_INDEX,
NONCLUS.INDEX_KEYS,
( NONCLUS.RANGE_SCAN_COUNT - CLUS.RANGE_SCAN_COUNT ) AS NONCLUSTERED_VS_CLUSTERED_RANGE_COUNT,
CLUS.USER_SEEKS                                      AS CLUSTERED_USER_SEEKS,
CLUS.USER_SCANS                                      AS CLUSTERED_USER_SCANS,
CLUS.SINGLETON_LOOKUP_COUNT                          AS CLUSTERED_SINGLE_LOOKUPS,
CLUS.RANGE_SCAN_COUNT                                AS CLUSTERED_RANGE_SCAN,
NONCLUS.USER_SEEKS                                   AS NONCLUSTERED_USER_SEEKS,
NONCLUS.USER_SCANS                                   AS NONCLUSTERED_USER_SCANS,
NONCLUS.SINGLETON_LOOKUP_COUNT                       AS NONCLUSTERED_SINGLE_LOOKUPS,
NONCLUS.RANGE_SCAN_COUNT                             AS NONCLUSTERED_RANGE_SCANS,
NONCLUS.USER_UPDATES                                 AS NONCLUSTERED_USER_UPDATES
FROM   INDEX_STATS_CURR_VW CLUS
INNER JOIN INDEX_STATS_CURR_VW NONCLUS
ON CLUS.TABLE_NAME = NONCLUS.TABLE_NAME
AND CLUS.DATABASE_NAME = NONCLUS.DATABASE_NAME
AND CLUS.SERVER_NAME = NONCLUS.SERVER_NAME
AND CLUS.INDEX_NAME <> NONCLUS.INDEX_NAME
WHERE  CLUS.INDEX_DESCRIPTION LIKE 'CLUSTERED%'
AND ( ( NONCLUS.RANGE_SCAN_COUNT > CLUS.RANGE_SCAN_COUNT )
OR ( NONCLUS.SINGLETON_LOOKUP_COUNT > CLUS.SINGLETON_LOOKUP_COUNT ) )
ORDER  BY CLUS.USER_LOOKUPS DESC,
CLUS.TABLE_NAME,
( NONCLUS.RANGE_SCAN_COUNT - CLUS.RANGE_SCAN_COUNT ) DESC

______________________________________________________________________________________________________

ANALYZE_INDEX_KEY_ORDER

SELECT ISV.SERVER_NAME, ISV.DATABASE_NAME,
ISV.TABLE_NAME, ISV.INDEX_NAME,
ISV.INDEX_KEYS AS CURRENT_INDEX_ORDER,
Stuff ((SELECT ', ' + KEYCOLUMN
FROM   INDEX_KEY_ORDER_VW IKO
WHERE  ISV.SERVER_NAME = IKO.SERVER_NAME
AND ISV.DATABASE_NAME = IKO.DATABASE_NAME
AND ISV.TABLE_NAME = IKO.TABLENAME
AND ISV.INDEX_NAME = IKO.INDEXNAME
ORDER  BY TABLENAME,
INDEXNAME,
TOTAL_ROWS DESC
FOR xml path('')), 1, 1, '') AS POTENTIAL_INDEX_ORDER
FROM   INDEX_STATS_CURR_VW ISV
WHERE  ISV.ROW_COUNT > 1000
AND ISV.USER_SEEKS > 1000
--AND ISV.TABLE_NAME = 'AX Table Name'
-- AND ISV.DATABASE_NAME = 'AX Database Name'
--AND ISV.SERVER_NAME = 'AX Database Server Name'

 ____________________________________________________________________________________________


 INDEXES_BEING_SCANNED

SELECT TOP 100 *
FROM   INDEX_STATS_CURR_VW
WHERE  USER_SCANS > 0
AND INDEX_DESCRIPTION LIKE 'NONCLUSTERED%'
ORDER  BY USER_SCANS DESC


____________________________________________________________________________________________________

SEARCH_QUERY_PLANS_FOR_INDEX_USAGE

;WITH FT_CTE2 (QUERY_PLAN_HASH, QUERY_PLAN)
AS
(SELECT QUERY_PLAN_HASH, QUERY_PLAN
FROM   QUERY_PLANS
WHERE  CONTAINS (C_QUERY_PLAN, '"INVENTDIM" AND "INDEX SCAN"') -- find all statements scanning a specific table
--WHERE  CONTAINS (C_QUERY_PLAN, '"I_6143RECID"') — find all SQL statements that contain a specific index
)
SELECT TOP 100 *
FROM   QUERY_STATS_CURR_VW QS -- Queries from last data collection only
--FROM   QUERY_STATS_VW QS -- Review queries for all data collections
INNER JOIN FT_CTE2 FT2
ON QS.QUERY_PLAN_HASH = FT2.QUERY_PLAN_HASH
WHERE  1 = 1
-- AND LAST_EXECUTION_TIME > 'XXXXXXX'   -- find all queries that have executed after a specific time
ORDER  BY TOTAL_ELAPSED_TIME DESC





Dynamics Perf 2.0 Installation for AX2012 R3 Performance


AX 2012  Performance monitoring tool and Installation Steps.


Please follow the below link for the installation step of Dynamics Perf 2.0.




https://blogs.msdn.microsoft.com/axinthefield/dynamicsperf-2-10-deployment-guide/

X++ Code to Hide (or) Restrict Enum values in the dialog in AX 2012 R3

X++ Code to Hide (or)  Restrict Enum values in the dialog in AX 2012 R3

// SalesStatus enum value to show only the BackOrder & Invoiced status in the dialog



public class RBHideEnumValues extends RunBaseBatch
{
          DialogEnumComboBox       dialogEnumComboBox;
          DialogField                            dialogSalesStatus;
          DialogRunBase                     dialog;
         SalesStatus                             salesStatus;

}

public Object dialog()
{
     
 Set                             enumSet = new Set(Types::Enum);
      DialogRunbase       dialog = super();
    ;
    dialogSalesStatus= dialog.addField(enumstr(SalesStatus));
    dialogSalesStatus.value(salesStatus);
    enumSet.add(SalesStatus::Backorder);
    enumSet.add(SalesStatus::Invoiced);
    dialogEnumComboBox =       DialogEnumComboBox::newParameters(null, dialogSalesStatus.control().id(), enumNum(SalesStatus), enumSet, dialog.form());
   return dialog;
}

X++ code to post multiple purchase order as single invoice (consolidated invoice)

X++ code to post multiple purchase order as single invoice (consolidated invoice) 




static void RB_invoiceConsolidatedPurchaseOrder(Args _args)
{
    Purchformletter_invoice         Purchformletter;
    vendPackingSlipJour             vendPackingSlipJour;
    TmpFrmVirtual                   tmpFrmVirtual;
    ParmId                          parmId;
    VendInvoiceInfoTable            vendinvoiceinfoTable;
    vendInvoiceInfoLine             vendInvoiceInfoLine;
    int                             i;
    container                       con;
    PurchId                         PurchId;
    purchline                       purchline;
    List                            selectedList = new List(Types::Record);
    ;
    try
    {
        

    //Getting the multiple purchase order in the container.
  // Here i am using 2 purchase order  12020015,12020016
        
        con+= ['12020015'];
        con+= ['12020016'];

        for (i = 1; i <= conLen(con); i++)
        {
            PurchId = conpeek(con,i);


            ttsbegin;
            tmpFrmVirtual.setTmp();
            
            // Add the packing slips into tmpFrmVirtual
            while select vendpackingslipjour
            where vendpackingslipjour.PurchId == PurchId
            {
                tmpFrmVirtual.clear();
                tmpFrmVirtual.TableNum = vendpackingslipjour.TableId;
                tmpFrmVirtual.RecordNo = vendpackingslipjour.RecId;
                tmpFrmVirtual.NoYes = NoYes::Yes;
                tmpFrmVirtual.Id = vendpackingslipjour.PurchId;
                tmpFrmVirtual.insert();

            }
            ttsCommit;
        }
        while select tmpFrmVirtual
        {
            selectedList.addEnd(tmpFrmVirtual);
        }
        
        // Construct form letter
        Purchformletter = purchformletter::construct(DocumentStatus::Invoice);
        
        // Add the packing slips to the purch form letter
        Purchformletter.selectFromJournal(selectedList.pack());

        purchformletter.sumBy(AccountOrder::Account);
        purchformletter.reArrangeNow(true);
        purchformletter.reArrange();
        purchformletter.specQty(purchupdate::ReceiveNow);
        purchformletter.parmNumber('EDICONS123');
        parmId = purchformletter.parmId();
        
        //Edit the vendInvoiceInfo table if you need it , otherwise this code is not needed.
        ttsBegin;
        select forupdate vendInvoiceInfoTable
         where vendinvoiceinfoTable.ParmId == parmId;
       
        vendInvoiceInfoTable.Num = 'EDICONS123';
        vendInvoiceInfoTable.VarianceApproved = NoYes::Yes;
        vendInvoiceInfoTable.DocumentDate = 15\03\2019;
        vendInvoiceInfoTable.FixedDueDate = 16\03\2019;
        vendInvoiceInfoTable.update();

        for (i = 1; i <= conLen(con); i++)
        {
            PurchId = conpeek(con,i);
                while select RecId from  purchline
                where purchline.PurchId == purchId &&
                    purchline.IsDeleted == NoYes::No
                {
                    vendInvoiceInfoLine = vendInvoiceInfoLine::findByPurchLineRefRecId(purchLine.RecId,true);
                    vendInvoiceInfoLine.update();
                }
            }
        ttsCommit;
        
        //Post the consolidated invoice.
        purchFormLetter.run();
        
        if (purchFormLetter.parmJournalRecord().recid)
        {
            info("Invoice completed sucessfully");
        }
    }
    catch
    {
    throw error(strFmt("Invoice failed"));
    }
}

Create or Update a Contact Information for a customer/vendor in X++ [AX 2012 R3]


Create or Update a Contact Information for a customer/vendor in X++ [AX 2012 R3]



static void RB_SupplierAddress_RoleUpdate(Args _args)
{
    VendTable                           VendTable = VendTable::find('‪‪‪PL00001');
    LogisticsElectronicAddress          logisticsElectronicAddress;
    container                           defaultRole = map2Con(LogisticsLocationEntity::getDefaultLocationRoleFromEntity(tableNum(DirPartyTable)));
   
   
    ttsBegin;
    logisticsElectronicAddress.Type = LogisticsElectronicAddressMethodType::Email;
    logisticsElectronicAddress.Locator = 'ramesh.balakrishnan@ao.com';
   
    logisticsElectronicAddress.Location = DirPartyLocation::findOrCreate(VendTable.Party, 0).Location;

    logisticsElectronicAddress = LogisticsElectronicAddress::findOrCreate(logisticsElectronicAddress);
   
    logisticsElectronicAddress = LogisticsElectronicAddress::findRecId(logisticsElectronicAddress.RecId, true);
   
    logisticsElectronicAddress.Description = "Official Email";
   
    logisticsElectronicAddress.IsPrimary = NoYes::Yes;
   
    logisticsElectronicAddress.update();
 
    LogisticsEntityLocationRoleMap::createEntityLocationRoles(tableNum(LogisticsElectronicAddressRole), logisticsElectronicAddress.RecId, conPeek(defaultRole, 1), true);
   
    info(strFmt("Created/updated phone number [%1] for customer %2.", logisticsElectronicAddress.Locator, VendTable.AccountNum));
    ttsCommit;
}

AX 2012 R3 - Fastest way to do full compile

Steps to do full compile in the fastest way in AX 2012 R3


Open the command prompt :

Traverse the path :

C:\Program Files\Microsoft Dynamics AX\60\Server\XXXXXXX\bin>
axbuild.exe xppcompileall /s=01 /altbin="C:\Program Files (x86)\Microsoft Dynamics AX\60\Client\Bin" /log:"C:\Temp

Here is an example command line for AxBuild.exe that might match the command line you need in your particular environment. The line is artificially wrapped here for better display.



Modify/ Update the Default Dimension in AX 2012 R3 Using X++ Code.



Modify/ Update the Default Dimension  in AX 2012 R3 Using X++ Code.

static void RB_ModifyDimension(Args _args)
{

    ItemId      itemId;
    str         costCentre;
 

    DimensionAttributeValueSetStorage   dimStorage = new DimensionAttributeValueSetStorage();
    DimensionAttribute                  dimAttribute;
    DimensionAttributeValue             dimAttributeValue;
    DimensionDefault                    defaultDimension;

    #define.CostCentre('CostCentre')

    itemid     =  "TESTItem";
    costCentre = '2000';

   
    ttsBegin;
    if (itemid && costCentre)
    {
       
        defaultDimension    = InventTable::find(itemId).DefaultDimension;
        dimStorage          = DimensionAttributeValueSetStorage::find(defaultDimension);
        dimAttribute        = DimensionAttribute::findByName(#CostCentre);
        dimAttributeValue   = DimensionAttributeValue::findByDimensionAttributeAndValue(dimAttribute, costCentre, true, true);

        dimStorage.addItem(dimAttributeValue);
        // Dimension modified or updated including costcentre as well.
        defaultDimension = dimStorage.save();
       
        info(strFmt("Modified/ Updated Default Dimension %1", defaultDimension));
       
    }
    ttsCommit;
                 
}










X++ code to Merge & Update Sales Order & Sales Line Financial Dimension.


X++ code to update Sales Order & Sales Line Financial Dimension.


static void RB_SLineFinDimensionUpdate(Args _args)
{
    SalesTable                          salesTable,salesTableUpd;
    SalesLine                           salesLine,salesLineUpd;
    CustAccount                      custAccount;
    DimensionDefault             defaultDimension,itemDefaultDimension,custDefaultDimension;


    salesTable   =  SalesTable::find('10000');
    salesLine    =  salesLine::find(salesTable.SalesId,1,true);

    if (salesLine)
    {
        itemDefaultDimension    =   InventTable::find(salesLine.ItemId).DefaultDimension;
        custDefaultDimension    =   CustTable::find(salesTable.CustAccount).DefaultDimension;

        defaultDimension        = DimensionDefaultingService::serviceMergeDefaultDimensions(custDefaultDimension,itemDefaultDimension);

        update_recordSet salesTableUpd
            setting defaultDimension =  custDefaultDimension
            where salesTableUpd.salesId == salesTable.SalesId;

            update_recordSet    salesLineUpd
                setting defaultDimension = defaultDimension
                where salesLineUpd.RecId == salesLine.RecId;

    }

}

How to: Add Filter Controls to a Simple List Form [AX 2012 R3]

How to: Add Filter Controls to a Simple List Form [AX 2012 R3]

 Step 1: 
Create a form filter 



Step: 2

Step: 3

int selectionChange()
{
    int ret;
    ret = super();

    QMPurchTable_ds.executeQuery();

    return ret;

}

Step: 4 
Write the below on FORM Datasource.

// If you are filtering used Enum Values. Please find the code below.
public void executeQuery()
{
   
    AXStatus      qmStatusExe; // EnumValue

   // converting the string value to enum
    qmStatusExe = str2enum(AXStatus,QMStatus.valueStr()); // values from the filter.

   qr =  SysQuery::findOrCreateRange(QMPurchTable_q.datasourceTable(tableNum(QMPurchTable)),fieldNum(QMPurchTable,QMStatus));

    if (qmStatusExe == AXStatus::Open)
    {
        qr.value(queryValue(AXStatus::Open));
    }
    else if (qmStatusExe == AXStatus::Closed)
    {
        qr.value(queryValue(AXStatus::Closed));
    }
    else
    {
        qr.value(SysQuery::valueUnlimited());
    }
    qr.status(RangeStatus::Locked);

    super();

}


X++ code to import multiple file from a folder in AX 2012 R3

X++ code to import multiple files from a folder in AX 2012 R3


Create a Runbase class :

class RBReadFileFromDirectory extends RunBaseBatch
{
    Filename                       ItemFileName;
    Filename                       filename;
    DialogField                   dialogFilename;
    SalesOrderTmp             salesOrderTmp;
    FilePath                        filePath;
    CommaTextIo               fileIO;
    Set                                 fileSet;
    CommaIo                      commaIo;
    str                                   currentFile;
    container                       lineCon;
    #File
    #AviFiles

    #define.CurrentVersion(1)
        #define.Version1(1)
        #localmacro.CurrentList
            fileName
        #endmacro
}

public Object dialog()
{
    DialogRunbase       dialog = super();
    ;

    dialogFilename   = dialog.addField(extendedTypeStr(FilenameOpen));
    dialogFilename.value(filename);

    return dialog;
}

public boolean getFromDialog()
{
    ;

    fileName = dialogFileName.value();
    return super();
}

public container pack()
{
    return [#CurrentVersion,#CurrentList];
}

public boolean unpack(container packedClass)
{
    Version version = runbase::getVersion(packedClass);
    ;
    switch (version)
    {
        case #CurrentVersion:
        [version,#CurrentList] = packedClass;
        break;
        default:
        return false;
    }
    return true;
}

public static RBReadFileFromDirectory construct()
{
    return new RBReadFileFromDirectory();
}

public static void main(Args _args)
{
    RBReadFileFromDirectory      readFileFromDirectory;

    ;
    readFileFromDirectory= RBReadFileFromDirectory::construct();

    if (readFileFromDirectory.prompt())
    {
        readFileFromDirectory.run();
    }

}

public void run()
{
    SetEnumerator           sE;
    int                              row = 1;


    if (!filename)     // check file exist from dialog
    {
        filePath =  @'C:\Test\';   // sepcify the folder path
        this.getFiles();
        sE = fileSet.getEnumerator();

        while (sE.moveNext()) // loop through all files
        {
            filename = sE.current();

            if (filename)
            {
                this.Process(filename);
            }

        }
    }
    else           // this code run for the file from dialog
    {
        this.Process(filename);
    }

}

private void getFiles()
{
    System.String[]                 files;
    System.Collections.IEnumerator  enumerator;
    str  file;

    try
    {
        fileSet = new  Set(Types::Container);
        files = System.IO.Directory::GetFiles(filePath, '*.csv');
        enumerator = files.GetEnumerator();

        while(enumerator.MoveNext())
        {
            file = enumerator.get_Current();
            fileSet.add([file]);
        }
    }
    catch (Exception::CLRError)
    {
        throw error ("Error");
    }

}


// Core Logic 

private void Process(Filename    _fileName)
{
    #File
    IO                                       iO;
    Container                           con;
    boolean                             first = true;
    SalesTable                          salesTable;
    SalesLine                           salesLine;
    FileIoPermission                    perm;

    ;

    perm = new FileIoPermission(_fileName, #IO_Read);
    perm.assert();

    iO = new CommaTextIo(_fileName,#IO_Read);

    if (! iO || iO.status() != IO_Status::Ok)
    {
        throw error("@SYS19358");
    }

    
    while (iO.status() == IO_Status::Ok)
    {
        con = iO.read();//  read file
        if (con)
        {
            if (first)  //skip header
            {
                first = false;
            }
            else
            {
                        salesOrderLogLinesIns.clear();
                        salesOrderTmp.SalesId                = conPeek(con, 1);
                        salesOrderTmp.CustAccount       = conPeek(con, 2);
                        salesOrderTmp.PaymMode         = conpeek(con, 3);
                        salesOrderTmp.insert();
               }
               
        }
    }


}
























D365 F&O - Restart PPAC UDE Environment

D365 F&O - Restart PPAC UDE Environment    As developers working in the Unified Development Experience (UDE) for Dynamics 365 Finance...