Resolving TFS synchronisation issues in AX 2012

Resolving TFS synchronisation issues in AX 2012

Tutorial
Published on
May 27, 2015

There are plenty of problems in AX 2012 in synchronising elements with TFS, especially when multiple developers are working on isolated environments and adding new code frequently.

This post will show you how to resolve most of the problems occurring after TFS synchronisation. Initially you should run synchronisation from development workspace from version control menu.

Problem 1

After synchronisation you should check for any elements that are not processed. You can check that from the ‘Synchronization log’ form or directly in the table ‘SysVersionControlSynchronizeLog’. Those elements that are not processed means that they are not imported correctly in your environment. The standard feature to process these is to run process from ‘Synchronization log’ form but the problem is that this processing routine tries to process the elements based on their batch id which is actually a group id. So the problem occurs when there are multiple elements with same batch id (Synchronisation). So there is a workaround to resolve this like update the batch id’s per type of elements i.e. check for last batch id and then start updating the elements e.g. for forms add batch id ‘3’. This can be cumbersome if you have plenty of not synchronised elements so for this reason I wrote a simple job that will easy the process:

static void updateNotSynced(Args _args)

{

   SysVersionControlSynchronizeLog         syncLog;

   SysVersionControlSynchronizeBatchNum    lastNum;

   boolean                                 entered = false;

   //find last batch num

   select firstOnly BatchNum from syncLog

   order by syncLog.BatchNum desc;

   lastNum = syncLog.BatchNum;

   setPrefix('Numbers to process:');

   //classes

   ttsBegin;

   while select forUpdate syncLog

       where syncLog.Processed == NoYes::No &&

             syncLog.ItemPath  like '*Classes*'

   {

       syncLog.BatchNum = lastNum + 1;

       syncLog.update();

       entered = true;

   }

   ttsCommit;

   if (entered)

   {

       lastNum++;

       entered = false;

       info(strFmt('Batch number for classes is: %1', lastNum));

   }

   //forms

   ttsBegin;

   while select forUpdate syncLog

       where syncLog.Processed == NoYes::No &&

             syncLog.ItemPath  like '*Forms*'

   {

       syncLog.BatchNum = lastNum + 1;

       syncLog.update();

       entered = true;

   }

   ttsCommit;

   if (entered)

   {

       lastNum++;

       entered = false;

       info(strFmt('Batch number for Forms is: %1', lastNum));

   }

   //tables

   ttsBegin;

   while select forUpdate syncLog

       where syncLog.Processed == NoYes::No &&

             syncLog.ItemPath  like '*Data dictionary*Tables*'

   {

       syncLog.BatchNum = lastNum + 1;

       syncLog.update();

       entered = true;

   }

   ttsCommit;

   if (entered)

   {

       lastNum++;

       entered = false;

       info(strFmt('Batch number for Tables is: %1', lastNum));

   }

   //extended data types

   ttsBegin;

   while select forUpdate syncLog

       where syncLog.Processed == NoYes::No &&

             syncLog.ItemPath  like '*Data dictionary*Extended data types*'

   {

       syncLog.BatchNum = lastNum + 1;

       syncLog.update();

       entered = true;

   }

   ttsCommit;

   if (entered)

   {

       lastNum++;

       entered = false;

       info(strFmt('Batch number for EDTs is: %1', lastNum));

   }

   //base enums

   ttsBegin;

   while select forUpdate syncLog

       where syncLog.Processed == NoYes::No &&

             syncLog.ItemPath  like '*Data dictionary*Base enums*'

   {

       syncLog.BatchNum = lastNum + 1;

       syncLog.update();

       entered = true;

   }

   ttsCommit;

   if (entered)

   {

       lastNum++;

       entered = false;

       info(strFmt('Batch number for Enums is: %1', lastNum));

   }

   //menu items

   ttsBegin;

   while select forUpdate syncLog

       where syncLog.Processed == NoYes::No &&

             syncLog.ItemPath  like '*Menu items*'

   {

       syncLog.BatchNum = lastNum + 1;

       syncLog.update();

       entered = true;

   }

   ttsCommit;

   if (entered)

   {

       lastNum++;

       entered = false;

       info(strFmt('Batch number for Menu items is: %1', lastNum));

   }

   //shared projects

   ttsBegin;

   while select forUpdate syncLog

       where syncLog.Processed == NoYes::No &&

             syncLog.ItemPath  like '*Projects*Shared*'

   {

       syncLog.BatchNum = lastNum + 1;

       syncLog.update();

       entered = true;

   }

   ttsCommit;

   if (entered)

   {

       lastNum++;

       entered = false;

       info(strFmt('Batch number for Shared projects is: %1', lastNum));

   }

   //jobs

   ttsBegin;

   while select forUpdate syncLog

       where syncLog.Processed == NoYes::No &&

             syncLog.ItemPath  like '*Jobs*'

   {

       syncLog.BatchNum = lastNum + 1;

       syncLog.update();

       entered = true;

   }

   ttsCommit;

   if (entered)

   {

       lastNum++;

       entered = false;

       info(strFmt('Batch number for Jobs is: %1', lastNum));

   }

   //SSRS Reports

   ttsBegin;

   while select forUpdate syncLog

       where syncLog.Processed == NoYes::No &&

             syncLog.ItemPath  like '*SSRS Reports*Reports*'

   {

       syncLog.BatchNum = lastNum + 1;

       syncLog.update();

       entered = true;

   }

   ttsCommit;

   if (entered)

   {

       lastNum++;

       entered = false;

       info(strFmt('Batch number for SSRS reports is: %1', lastNum));

   }

   //Security roles

   ttsBegin;

   while select forUpdate syncLog

       where syncLog.Processed == NoYes::No &&

             syncLog.ItemPath  like '*Security*Roles*'

   {

       syncLog.BatchNum = lastNum + 1;

       syncLog.update();

       entered = true;

   }

   ttsCommit;

   if (entered)

   {

       lastNum++;

       entered = false;

       info(strFmt('Batch number for Roles is: %1', lastNum));

   }

   //Security duties

   ttsBegin;

   while select forUpdate syncLog

       where syncLog.Processed == NoYes::No &&

             syncLog.ItemPath  like '*Security*Duties*'

   {

       syncLog.BatchNum = lastNum + 1;

       syncLog.update();

       entered = true;

   }

   ttsCommit;

   if (entered)

   {

       lastNum++;

       entered = false;

       info(strFmt('Batch number for Duties is: %1', lastNum));

   }

   //Security privileges

   ttsBegin;

   while select forUpdate syncLog

       where syncLog.Processed == NoYes::No &&

             syncLog.ItemPath  like '*Security*Privileges*'

   {

       syncLog.BatchNum = lastNum + 1;

       syncLog.update();

       entered = true;

   }

   ttsCommit;

   if (entered)

   {

       lastNum++;

       entered = false;

       info(strFmt('Batch number for Privileges is: %1', lastNum));

   }

   //Menus

   ttsBegin;

   while select forUpdate syncLog

       where syncLog.Processed == NoYes::No &&

             syncLog.ItemPath  like '*Menus*'

   {

       syncLog.BatchNum = lastNum + 1;

       syncLog.update();

       entered = true;

   }

   ttsCommit;

   if (entered)

   {

       lastNum++;

       entered = false;

       info(strFmt('Batch number for Menus is: %1', lastNum));

   }

   //Services

   ttsBegin;

   while select forUpdate syncLog

       where syncLog.Processed == NoYes::No &&

             syncLog.ItemPath  like '*Services*'

   {

       syncLog.BatchNum = lastNum + 1;

       syncLog.update();

       entered = true;

   }

   ttsCommit;

   if (entered)

   {

       lastNum++;

       entered = false;

       info(strFmt('Batch number for Services is: %1', lastNum));

   }

   //Code permissions

   ttsBegin;

   while select forUpdate syncLog

       where syncLog.Processed == NoYes::No &&

             syncLog.ItemPath  like '*Security*Code permissions*'

   {

       syncLog.BatchNum = lastNum + 1;

       syncLog.update();

       entered = true;

   }

   ttsCommit;

   if (entered)

   {

       lastNum++;

       entered = false;

       info(strFmt('Batch number for Code permissions is: %1', lastNum));

   }

   //Workflow

   ttsBegin;

   while select forUpdate syncLog

       where syncLog.Processed == NoYes::No &&

             syncLog.ItemPath  like '*Workflow*'

   {

       syncLog.BatchNum = lastNum + 1;

       syncLog.update();

       entered = true;

   }

   ttsCommit;

   if (entered)

   {

       lastNum++;

       entered = false;

       info(strFmt('Batch number for Workflows is: %1', lastNum));

   }

   //Parts

   ttsBegin;

   while select forUpdate syncLog

       where syncLog.Processed == NoYes::No &&

             syncLog.ItemPath  like '*Parts*'

   {

       syncLog.BatchNum = lastNum + 1;

       syncLog.update();

       entered = true;

   }

   ttsCommit;

   if (entered)

   {

       lastNum++;

       entered = false;

       info(strFmt('Batch number for Parts is: %1', lastNum));

   }

}

The job above updates ‘SysVersionControlSynchronizeLog’ and shows the batch numbers to process in an ‘Infolog’ window. So after running the job you can filter by batch id and click ‘Process’ button:

Problem 2

Cannot import elements that have same origin keys as existing elements in Dynamics AX 2012.

This problem occurs when some of the elements are renamed by other developer and by default the origin keys remain the same. So find the elements that cause the issues in your local repository, right click for properties, un-check the flag ‘Read only’:

Then open the ‘XPO’ file and delete all the lines with origin id:

This will modify only your local copies of the elements so don’t worry, the good versions are always on TFS. Next, run the process from ‘Synchronization log’ form again for the same type of elements that caused the problems before.

Problem 3

SQL server is throwing errors as it cannot execute any command on the particular table. This is caused because of change of indexes on the table which already had some data populated and validated by some old index. The solution to this is running the following job to delete records with duplicate keys:

static void KLFRemoveDuplicates(Args _args)

{

   Set fieldSet = new set(Types::Integer);

   DictIndex  dictIndex = new DictIndex(

       tablenum(KLFTestDuplicates),

       indexnum(KLFTestDuplicates, AUniqueIdx));

   int i;

   ;

   if(dictIndex.numberOfFields())

   {

       for(i=1;i<=dictIndex.numberOfFields();i++)

       {

           fieldSet.add(dictIndex.field(i));

       }

       ReleaseUpdateDB::indexAllowDup(dictIndex);

       ReleaseUpdateDB::deleteDuplicatesUsingIds(tablenum(KLFTestDuplicates), 0, fieldSet);

       ReleaseUpdateDB::indexAllowNoDup(dictIndex);

   }

   info("done");

}

Be careful with this as it deletes records in your chosen table.

Happy DAXing.

Share it
Written by
Viktor Ristkovski

Microsoft Dynamics developer since 2009 (starting from Dynamics AX 2009) with experience in multiple Dynamics implementations across Europe and in almost any technical area of Microsoft Dynamics. Comes from Skopje, Macedonia, Graduated at Technical University in Varna, Bulgaria as Computer Science Engineer. Always supported by his lovely wife and daughter.