Skip to main content

Installing Feature pack on AX 2012 CU3

I recently had to install the Feature pack on CU3 environment and I thought I should share my experience.

Note: I won’t go through every single detail. Just the database and models.

 

  • Install the Feature pack for the Database using the full Feature pack disc

This will warn you that you have a new model and it may override

clip_image001

  • Install CU3 and this should import a model "Extensions update" model
  • Install your custom model - merge whatever you need to merge
  • Start your AOS - make sure your service and client are both on the CU3 build
  • Run the client and you should see "The model store has been modified" pop up

Select "Start the software update checklist" since this is a minor update

clip_image002

  • The software update checklist will start

Follow the check list - I did my merge already before so I didn’t run the "Detect code upgrade conflicts". I marked it as compete. You could do yours here.

clip_image003

  • When you get to the "Synchronize database" it may fail. It failed for me because of ID conflicts.

clip_image004

I did a bit of digging and found the application id and the SQL dictionary id was different.

clip_image005

It renumbered the field DueDateLimitGroupId_ES (Foundation KB2709934 which is CU3) from 60001 to 60003.
Then the new field "DefaultPaymTerm_PSN" (Extensions model) took 60001. That is why it tries to run an update of the field and failed.

Only thing I can point it at is that when I installed the feature pack. It removed CU3, then installed the feature pack. Which took the first free ID. Then when I installed CU3, it took the next free ID.

  • To fix this issue I had to run a clean up job to update the SQL dictionary with the new ID. If there is a name match.

Thanks to this blog http://dev.goshoom.net/en/2011/11/id-change/

I made a small change to skip views as that didn’t work.

static void UpdateDataDictionaryID(Args _args)
{
Dictionary dictionary = new Dictionary();
SysDictTable dictTable;
DictField dictField;
TableId tableId;
FieldId fieldId;
SqlDictionary sqlDictionaryTable;
SqlDictionary sqlDictionaryField;   setPrefix("Update of data dictionary IDs");
tableId = dictionary.tableNext(0);
ttsbegin;   while (tableId)
{
dictTable = new SysDictTable(tableId);
setPrefix(dictTable.name());   if (!dictTable.isSystemTable() && !dictTable.isView())
{
//Finds table in SqlDictionary by name in AOT, if ID was changed.
//Empty field ID represents a table.
select sqlDictionaryTable
where sqlDictionaryTable.name == dictTable.name()
&& sqlDictionaryTable.fieldId == 0
&& sqlDictionaryTable.tabId != dictTable.id();   if (sqlDictionaryTable)
{
//Updates table ID in SqlDictionary
if (ReleaseUpdateDB::changeTableId(
sqlDictionaryTable.tabId,
dictTable.id(),
dictTable.name()))
{
info(strFmt("Table ID changed (%1 -> %2)", sqlDictionaryTable.tabId, dictTable.id()));
}
}   fieldId = dictTable.fieldNext(0);   //For all fields in table
while (fieldId)
{
dictField = dictTable.fieldObject(fieldId);   if (dictField.isSql() && !dictField.isSystem())
{
//Finds fields in SqlDictionary by name and compares IDs
select sqlDictionaryField
where sqlDictionaryField.tabId == dictTable.id()
&& sqlDictionaryField.name == dictField.name()
&& sqlDictionaryField.fieldId != 0
&& sqlDictionaryField.fieldId != dictField.id();   if (sqlDictionaryField)
{
//Updates field ID in SqlDictionary
if (ReleaseUpdateDB::changeFieldId(
dictTable.id(),
sqlDictionaryField.fieldId,
dictField.id(),
dictTable.name(),
dictField.name()))
{
info(strFmt("Field %1 - ID changed (%2 -> %3)",
dictField.name(),
sqlDictionaryField.fieldId,
dictField.id()));
}
}
}
fieldId = dictTable.fieldNext(fieldId);
}
}
tableId = dictionary.tableNext(tableId);
}
ttscommit;   info("Done");
}


  • To deal with the views as that will error out with the sync (very weird errors but the Event viewer errors are more helpful), I wrote a job to give me an info log to drop all the views. If you are concerned about this - you can just run the sync and get a list of views it fails on and just delete them one at a time.

Copy the infolog and run it in SQL.






static void ShowViewDataDictionaryID(Args _args)
{
Dictionary dictionary = new Dictionary();
SysDictTable dictTable;
DictField dictField;
TableId tableId;
FieldId fieldId;
SqlDictionary sqlDictionaryTable;
SqlDictionary sqlDictionaryField;   tableId = dictionary.tableNext(0);   while (tableId)
{
dictTable = new SysDictTable(tableId);
setPrefix(dictTable.name());   if (!dictTable.isSystemTable() && dictTable.isView())
{
info(strFmt("DROP VIEW %1",dictTable.name()));   }
tableId = dictionary.tableNext(tableId);
}   info("Done");
}



  • Continue the sync. This time it should work fine. You may have to do it twice to get it to work smooth. This is because of the views deleted may have dependencies.

  • Complete the remaining checklist tasks

Good to see someone had a similar issue recently.

http://sashanazarov.blogspot.com/2012/09/id-change-in-dynamics-ax-data-dictionary.html

 

Final thought.

We should go with the Feature pack with all new clients. Microsoft has already put a strong statement out on partner source on why we should adopt it.

The Microsoft Dynamics AX 2012 Feature Pack—Why You Should Adopt it Moving Forward

"When Microsoft Dynamics AX 2012 R2 is released in H1 Calendar Year 2013 it will automatically install the remaining Microsoft provided country localizations (Asia, South America, Eastern Europe, etc.); so that we maintain the single consistent Microsoft Dynamics AX 2012 code base."

From this statement, it looks like Microsoft will release a single code base and their won't be a choice in the future. Everything will be installed, including the industry solutions (ie. Feature pack).

Popular posts from this blog

AX - How to use Map and MapEnumerator

Similar to Set class, Map class allows you to associate one value (the key) with another value. Both the key and value can be any valid X++ type, including objects. The types of the key and the value are specified in the declaration of the map. The way in which maps are implemented means that access to the values is very fast. Below is a sample code that sets and retrieves values from a map. static void checkItemNameAliasDuplicate(Args _args) { inventTable inventTable; Map map; MapEnumerator mapEnumerator; NameAlias nameAlias; int counter = 0; ; map = new Map(Types::String, Types::Integer); //store into map while select inventTable { nameAlias = inventTable.NameAlias; if (!map.exists(nameAlias)) { map.insert(nameAlias, 1); } else { map.insert(nameAlias, map.lookup(nameAlias) + 1); } } //retrieve fro

AX - How to use Set and SetEnumerator

The Set class is used for the storage and retrieval of data from a collection in which the values of the elements contained are unique and serve as the key values according to which the data is automatically ordered. You can create a set of primitive data types or complex data types such as a Class, Record or Container. Below is sample of a set of records. static void _Set(Args _args) {     CustTable       custTable;     Set             set = new Set(Types::Record);     SetEnumerator   setEnumerator;     ;     while select custTable     {         if (custTable && !set.in(custTable))         {             set.add(custTable);         }     }     if (!set.empty())     {         setEnumerator = set.getEnumerator();         setEnumerator.reset();         while (setEnumerator.moveNext())         {             custTable = setEnumerator.current();             info(strfmt("Customer: %1",custTable.AccountNum));         }     } } Common mistake when creating a set of recIds

Approve Workflow via email using template placeholders #Dyn365FO

Dynamics 365 for Finance and Operations has placeholders which can be inserted into the instructions. Normally you would want this to show up in the email that is sent. One of the most useful ones is the URL link to the exact record that you are approving. In the workflow configurations use the placeholder and build up your message. Towards the end it has workflow specific ones. The URL token is %Workflow.Link to web% . For the technical people the token is replaced in this class WorkflowDocumentField. This is what I inserted into my email template. <BODY> subject: %subject% <BR> message: %message% <BR> company: %company% <BR> for: %for% <BR> </BODY> Should look like this. The final result looks like this. If you debug these are the place holders that are put together.