How To Write Data Upgrade Scripts For Microsoft Dynamics AX 2009
How To Write Data Upgrade Scripts For Microsoft Dynamics AX 2009
Microsoft Dynamics AX
[This document describes how to use the Microsoft Dynamics™ AX Data Upgrade
Framework and to write data upgrade scripts for customer data upgrade data models
(Microsoft Dynamics AX tables).]
http://www.microsoft.com/dynamics/ax
Contents
Introduction ..............................................................................................................4
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Appendix 1: Guidelines for Writing Direct SQL in Upgrade Scripts ..............28
Using Set-Based Updates in X++ ............................................................................................................................ 28
Executing Direct SQL from X++ ............................................................................................................................ 29
How to Execute Direct SQL for X++ .................................................................................................................. 29
Best Practices Warning when Executing Direct SQL ........................................................................................ 29
Using Utility Functions to Execute Direct SQL .................................................................................................. 30
Documenting Direct SQL ................................................................................................................................... 30
Using Table Names in Direct SQL ..................................................................................................................... 30
Adding Literals in Direct SQL ............................................................................................................................. 30
Specifying DataAreaId in Where-Clauses ......................................................................................................... 31
Determining Whether a Table or Field Exists in the Database ....................................................................... 32
Defining String Lengths ..................................................................................................................................... 33
Applying LTrim for String Comparisons in the WHERE Clause ........................................................................ 33
Oracle Only: Applying NLS_LOWER on String Columns in the WHERE Clause ............................................... 33
Structuring an Upgrade Script for Managing SQL Server and Oracle ............................................................. 34
Implementing Complex Inserts and Updates in Direct SQL ................................................................................... 35
Creating Stored Procedures and Functions ..................................................................................................... 35
Implementing Set-Based Updates with Joins ................................................................................................... 36
Using Direct SQL for Set-Based Updates .......................................................................................................... 37
Using a Set-Based Insert Operation .................................................................................................................. 38
Number Sequence Considerations ................................................................................................................... 39
RECID in Dynamics AX 5.0 ................................................................................................................................ 39
Assigning RECID on INSERT .............................................................................................................................. 40
Looking Up Table ID and Field IDs .................................................................................................................... 41
Assigning Business Sequences on Insert ......................................................................................................... 41
Calling FN_FMT_NUMBERSEQUENCE .............................................................................................................. 44
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Introduction
This document describes how to use the Microsoft Dynamics™ AX Data Upgrade Framework and to write data
upgrade scripts for customer data upgrade data models (Microsoft Dynamics AX tables). The data upgrade
framework can be used to perform data correction or data transformation.
The intended audience for this document is Microsoft Dynamics AX application developers.
This document is based on Leveraging the Microsoft Dynamics AX2009 Data Upgrade Framework, a Microsoft
Dynamics AX2009 Technical Information document, and on the Microsoft Dynamics AX 2009 Data Upgrade
Framework. It has been updated for the new data upgrade framework and Best Practices for performance.
Out of the box – Dynamics Ax 2009 supports upgrading data from Dynamics Axapta 3.0 and Dynamics Ax 4.0
to Dynamics Ax 2009.
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
When is a Data Upgrade Script Needed?
There are changes that can be made in the data model without the need for an upgrade script, and there are
changes that need an upgrade script.
The following changes can be made without an upgrade script:
1. Change the name of a field
2. Change the name of a table
3. Add a field to a table with a default value for every field
4. Add/change relations
5. Add/change non-unique indexes
6. Add/change delete actions
7. Add/change/delete temporary table
The following changes require an upgrade script:
1. Delete a table and save data
2. Delete a field and save data
3. Add/change unique indexes
4. Change a non-unique index into a unique index
5. Restructure where data is stored. For example, moving data from one field to another
6. Correct old data inconsistencies
7. Populate new tables with existing data
8. Populate new fields with existing data or a default value that is different from the default value for the
data type
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
How to Upgrade Data for a Major Release or Service Pack
The Upgrade Checklist is a navigation pane that guides you through the data upgrade steps. It is invoked
automatically when Microsoft Dynamics AX starts after a service pack or major release is installed. Data
upgrade is performed using the Upgrade Checklist in the following order:
1. Presynchronize
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
2. Postsynchronize
3. Upgrade additional features
The data upgrade framework drives the data upgrade scripts that transform an older version of the
Microsoft Dynamics AX database to the new version. These steps are described in later sections.
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
The Data Upgrade Framework
The data upgrade framework gives developers the infrastructure to insert data upgrade scripts written in X++.
The data upgrade framework manages the dependencies of the scripts, schedules them to be run in parallel by
batch clients, and provides progress reports on the running scripts. The data upgrade framework has a built-in
error recovery mechanism that helps to ensure system integrity when the upgrade has to be resumed after an
error.
With the exception of the base ReleaseUpdateDB class, the ReleaseUpdateDB* classes contain implementations
of data upgrade scripts. The scripts provide abstract methods and utility functions for data upgrade classes. The
class diagram of the upgrade script classes is shown in Figure 2.
ReleaseUpdateDB (SYS)
# addStandardJob()
+ addDependency()
+ addCrossModuleDependency()
+ moduleName()
+ initPreSyncJobs()
+ initPostSyncJobs()
+ run()
TAP 3 ships
Release 4.0
V40 classes for ReleaseUpdateDB40_Asset (SYS)
upgrade to AX 4.0
In AX 4.0, no data
+ initPreSyncJobs() upgrade in the
+ initPostSyncJobs() Ledger module
+ upgradeScript403()
+ upgradeScript404()
+ initPreSyncJobs()
+ initPostSyncJobs()
+ upgradeScript41SP1()
+ upgradeScript41SP2()
AX 4.0 SP2 ships
Release 5.0
V41 classes for
upgrade to AX 5.0 ReleaseUpdateDB41_Asset (SYS) ReleaseUpdateDB41_Ledger (SYS)
include service
pack upgrade
scripts + initPreSyncJobs() + initPreSyncJobs()
+ initPostSyncJobs() + initPostSyncJobs()
+ upgradeScript41SP1() + upgradeScript415()
+ upgradeScript41SP2() + upgradeScript416()
+ upgradeScript413()
AX 5.0 ships
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Data Upgrade Scripts by Module
Data upgrade scripts are inserted into the data upgrade as methods of a ReleaseUpdateDB<NN>_<module>
class, where <NN> is the version of Microsoft Dynamics AX being upgraded to, and <module> is the module
name the script belongs to. These classes are derived from the base class ReleaseUpdateDB and are connected
to the data upgrade framework.
When you create upgrade scripts for your version of Microsoft Dynamics AX, you can use any of the new
classes in the following table according to your script's application module and the version you are developing.
TAP3 (39) 401 41
ReleaseUpdateDB39_Administration ReleaseUpdateDB401_Administration ReleaseUpdateDB41_Administration
ReleaseUpdateDB39_Asset ReleaseUpdateDB401_Bank ReleaseUpdateDB41_Asset
ReleaseUpdateDB39_Bank ReleaseUpdateDB401_COS ReleaseUpdateDB41_Bank
ReleaseUpdateDB39_Basic ReleaseUpdateDB401_Cust ReleaseUpdateDB41_Basic
ReleaseUpdateDB39_COS ReleaseUpdateDB401_Ledger ReleaseUpdateDB41_COS
ReleaseUpdateDB39_Cust ReleaseUpdateDB401_Proj ReleaseUpdateDB41_Cust
ReleaseUpdateDB39_HRM ReleaseUpdateDB401_Vend ReleaseUpdateDB41_HRM
ReleaseUpdateDB39_Invent ReleaseUpdateDB41_Invent
ReleaseUpdateDB39_Jmg ReleaseUpdateDB41_Jmg
ReleaseUpdateDB39_KM ReleaseUpdateDB41_KM
ReleaseUpdateDB39_Ledger ReleaseUpdateDB41_Ledger
ReleaseUpdateDB39_PBA ReleaseUpdateDB41_Prod
ReleaseUpdateDB39_Prod ReleaseUpdateDB41_Proj
ReleaseUpdateDB39_Proj ReleaseUpdateDB41_Req
ReleaseUpdateDB39_Req ReleaseUpdateDB41_SMA
ReleaseUpdateDB39_SMA ReleaseUpdateDB41_smm
ReleaseUpdateDB39_smm ReleaseUpdateDB41_Trv
ReleaseUpdateDB41_Vend
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Figure 3. Upgrade Classes in the Applications Object Tree
10
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
SYS Versions and Data Upgrade of Interim SYS releases
The SYS layer contains the core functionality of Microsoft Dynamics AX. A modification to this layer is
shipped to partners and customers in beta versions (for example, Microsoft Dynamics AX 4.0 TAP3), final
release version (for example, Microsoft Dynamics AX 4.0), and refresh versions of major releases (for example,
Microsoft Dynamics AX 4.0.1), referred to here as interim SYS releases. The data upgrade framework supports
upgrades that span multiple SYS releases by providing the infrastructure to incrementally upgrade from one
SYS release to another, later release.
In order to incrementally upgrade from a SYS release that is two or more versions earlier, the
initPreSyncJobs, initPostSyncJobs and initAdditionalJobs methods must be overridden
11
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
and you must call “#initSyncJobsPrefix” to include the previous upgrade. The initPreSyncJobs,
initPostSyncJobs and initAdditionalJobs jobs detect the earlier (“from”) version of the upgrade
and skips if necessary.
void initPostSyncJobs()
{
#initSyncJobsPrefix
Finally, the purpose of an individual script is to upgrade a table's data from SysVer -1 to SysVer. Each
script is used to upgrade the data to the current version.
For example, in Figure 2, a data upgrade script in the Fixed Asset module for the Service Pack for
Microsoft Dynamics AX 4.0.1 is implemented in the ReleaseUpdateDB41_Asset class in the SYP layer. This
script will be merged with the data upgrade scripts for SYS release Microsoft Dynamics AX 4.1 into
ReleaseUpdateDB41_Asset in the SYS layer. The data upgrade framework handles service pack releases by
detecting at individual script level what has been run already in a service pack of the previous SYS release and
skips the upgrade script.
void initPostSyncJobs()
{
#initSyncJobsPrefix
…
// Add service pack upgrade scripts, for example, upgradeScript413
}
void initPostSyncJobs()
{
#initSyncJobsPrefix
12
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
// Add SYS upgrade scripts, including overlaid upgrade scripts
…
// Add new DIS upgrade scripts, not DIS overlaid upgrade scripts
…
// Add new VAR upgrade scripts, not VAR overlaid upgrade scripts
}
Create a single upgrade script that combines changes across multiple product versions
When upgrading to version n (target) from version n-2 (source), you can sometimes provide an algorithm that
upgrades data directly from the source to the target version without upgrading to the interim version. We call
these algorithms combined upgrade scripts. In cases for which you can create a combined upgrade script, follow
the best practices below:
1. Place the algorithm in the upgrade class for the source version, replacing the original algorithm. For
example, if you are upgrading from version 3.0 to 4.0 SP1, put the combined algorithm in the
ReleaseUpdateDB39 class.
2. Put a condition in a script in the upgrade class for the target version, setting it to execute only if you are
not upgrading from the source version. For example, change the script in the 4.0 SP1 version to
3. public void updateCustTrans()
4. {
5. if (ReleaseUpdateDB::getFromVersion() != sysReleasedVersion::v30)
6. {
7. Original script logic for upgrade from 4.0 to 4.0 SP1
8. }
9. }
13
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Data Upgrade Scripts
Data upgrade scripts comprise the majority of the data upgrade framework. For each version, a set of classes
exists - one upgrade class per module. Currently, there are 18 application modules for upgrade scripts. They are
named ReleaseUpdateDB<version>_<module>, for example ReleaseUpdateDB39_Bank.
Each of these classes contains scripts for pre-synchronization, post-synchronization and additional upgrades.
The scripts are scheduled by the initPreSyncJobs, initPostSyncJobs and
initAdditionalJobs methods respectively.
Each class can handle your upgrade script in one of four different ways - Start, Shared, Normal (also called
Standard ), and Final. Note that it is important to choose the right one so that the script runs at the correct time
and in the correct manner:
Pre-synchronization Post-synchronization Additional upgrade
Start (allow duplicates) -
14
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Standard scripts are run once per company and are used to update company specific tables. (~90% of
all scripts are of this type)
8. Postsynchronize Final scripts
(Executed last)
Final scripts are used to undo changes to indexes that were made to allow duplicates using the pre-
synchronization start script. Final scripts are run only once, as compared to normal scripts, which are
run once per company.
1. Upgrade additional features scripts
Upgrade additional features scripts are used to upgrade of the non-core functionality after the
functional data upgrade
15
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Writing Data Upgrade Scripts
To create a script you need to create a method on the appropriate class. For example, for
Microsoft Dynamics AX 4.0 TAP3 the class is ReleaseUpdateDB39_<module>. You must also inform the
framework how to handle the script. This is done by adding a line in the initPreSyncJobs or
initPostSyncJobs or initAdditionalJobs method on the class. Each of these
ReleaseUpdateDBxx_xxx classes contains three separate methods you can modify to schedule your jobs –
initPreSyncJobs, initPostSyncJobs and initAdditionalJobs. If you would like your job to
run in pre-synchronize phase, add it to the initPreSyncJobs method, otherwise add it to the
initPostSyncJobs method or to the initAdditionalJobs method for the additional feature upgrade.
The following are script templates you can use:
this.addStartJob(methodStr(<ClassName>, <MethodName>), "description",
[configurationkeynum(ConfigurationKey1), …, configurationkeynum(ConfigurationKey1) ]);
this.addStandardJob(methodStr(<ClassName>, <MethodName>),
"description",[configurationkeynum(ConfigurationKey1), …, configurationkeynum(ConfigurationKey1) ]);
this.addFinalJob(methodStr(<ClassName>, <MethodName>),
"description",[configurationkeynum(ConfigurationKey1), …, configurationkeynum(ConfigurationKey1) ]);
this.addFinalJob(methodstr(ReleaseUpdateDB39_Administration, allowDupSysExpImpTableGroupIdx),
"@SYS97945", [configurationkeynum(Asset), configurationkeynum(Bank) ]);
Also, you can specify a set of configuration keys on the module level by using the setModuleConfigKey
function. The module configuration key set is joined with each upgrade script configuration key set for that
module.
this.setModuleConfigKey([configurationkeynum(ConfigurationKey1),…,configurationkeynum(Configur
ationKey1) ])
Note that if you are using setModuleConfigKey, it should be called from InitPreSyncJobs, initPostSyncJobs and
InitAdditionalJobs method separately.
16
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Script Dependencies
You can also add dependencies between your scripts. This can be useful to avoid locking and for enforcing a
logical flow of your scripts. To add a dependency, include the following in the appropriate InitXXXJobs
method:
this.addDependency(methodStr(<ClassName>, <MethodName>),
methodStr(<ClassName>, <MethodName>));
where the first method must be executed before the second method executes.
1. If you have a dependency between the scripts inside a module, use the addDependency method.
2. If the script is dependant on another module’s script, you can use the
addCrossModuleDependency method to ensure a correct execution sequence between scripts
placed in the different classes:
this.addCrossModuleDependency(classnum(<ClassName>), methodStr(<ClassName>, <MethodName>),
classnum <ClassName>, methodStr(<ClassName>, <MethodName>));
1. If the script is dependant on another module’s script from a previous version, you can use the
addCrossVersionModuleDependency method to ensure that the correct execution sequence
between scripts placed in the different versions and modules:
this.addCrossVersionModuleDependency(
classnum(<ClassName>),
methodStr(<ClassName>, <MethodName>),
SysReleaseVersion::<version>,
classnum <ClassName>,
methodStr(<ClassName>, <MethodName>),
SysReleasedVersion::<version>);
2. If a script is dependent on another script from a previous version but located in the same module,
then you don‟t need a dependency, as the upgrade framework automatically provides implicit
dependency in that case.
17
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Here is an example of the dependency tree:
Start scripts
ReleaseUpdateDB39_Asset ReleaseUpdateDB39_Ledger
Start scripts Start scripts
… more classes
for other modules
ReleaseUpdateDB401_Asset ReleaseUpdateDB401_Ledger
Start scripts Start scripts
ReleaseUpdateDB41_Asset ReleaseUpdateDB41_Ledger
Start scripts Start scripts
Standard and
Shared scripts
ReleaseUpdateDB39_Asset ReleaseUpdateDB39_Ledger
Standard and Shared scripts le
odu Standard and Shared scripts
sm
ros cy
t om C nden
s epe
Cu
f a sion d
le o r
mp s ve
ReleaseUpdateDB401_Asset Exa cros
ReleaseUpdateDB401_Ledger
Standard and Shared scripts … more classes Standard and Shared scripts
for other modules
Final scripts
ReleaseUpdateDB39_Asset ReleaseUpdateDB39_Ledger
Final scripts Final scripts
ReleaseUpdateDB401_Asset ReleaseUpdateDB401_Ledger
Final scripts … more classes Final scripts
for other modules
ReleaseUpdateDB41_Asset ReleaseUpdateDB41_Ledger
Final scripts Final scripts
18
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
SysInetThemeTable
SysImageTable
SysPersonalization
LanguageTable
Batch
BatchGroup
SysLicenseCodeSort
DocuParameters
SysSecurityFormTable
SysSecurityFormControlTable
SysEvent
KMConnectionType
SalesParmUpdate
SalesParmSubTable
PurchParmUpdate
PurchParmSubTable
SysVersionControlParameters
ReleaseUpdateScripts
ReleaseUpdateScriptDependency
ReleaseUpdateJobStatus
DocuOpenFile
CompanyInfo
For these special tables, you cannot use pre-synchronization Start scripts. So, if you change field ID on one of
these tables, code changes must be made directly in the \Classes\Application\syncApplTables()
method, for example:
if (!this.isRunningMode())
{
ttsbegin;
if (isConfigurationkeyEnabled(configurationkeynum(CRSEGermany)))
{
ReleaseUpdateDB::changeFieldByName('TaxRepresentative', 41, 0, 75);
}
ttscommit;
}
syncTable(tablenum(CompanyInfo));
Note that changes in Application class are risky and should be made with caution.
19
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Best Practices for Writing Data Upgrade Scripts
20
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Coding Best Practices
Indicating Progress
To supply progress status, you can use a simplified version operation progress by calling:
this.tableProgress(<tableId>);
and including the table-ID for the table you have just updated. This should only be called once in each
outermost loop (even if you are updating several tables in the inner loops).
Documenting Scripts
You should include meaningful comments in each data upgrade script to explain the functionality of the script.
Unique Indexes
It is important that the database can synchronize without errors when the customer upgrades. Three scenarios
require special attention when dealing with index changes:
1. Removing a field from a unique index
2. Adding a new unique index
3. Making a non-unique index unique, (setting the AllowDuplicates property to false)
All these scenarios make an index more restrictive and will cause the synchronization to fail if not handled
properly.
The easiest solution is to delete the data that collides with the index. This should only be done in situations
where it doesn't make sense to keep the duplicate records. This is performed using one of the following options:
21
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Add this script to the shared pre-synchronization jobs. It will execute across companies even for company
specific tables.
Note that if the table or fields have changed names or IDs from one version to another, you have to use option 2
instead.
Option 2. When the duplicate records contain values that need more complex logic to clean up, the solution is
more involved:
1. Create a start pre-synchronization upgrade script. This will change the index to allow duplicates:
DictIndex dictIndex = new
DictIndex(TableNum(<TableName>),indexNum(<TableName>,<IndexName>));
;
ReleaseUpdateDB::indexAllowDup(dictIndex);
2. Create a normal upgrade script. This will move the data according to the new data model.
3. Create a final post-synchronization upgrade script. This will change the index to not allow duplicates:
DictIndex dictIndex = new
DictIndex(TableNum(<TableName>),indexNum(<TableName>,<IndexName>));
;
ReleaseUpdateDB::indexAllowNoDup(dictIndex);
Note this action will delete across companies even for company-specific tables and it is the fastest way to
perform the operation.
Alternatively, you can create a pre-synchronization normal script using the delete_from construct.
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Microsoft Corporation shipped configuration key in custom tables, and if the configuration key is deleted, the
table will be lost during synchronization.
Later in the upgrade script, you can use that method to get the actual number sequence
num = NumberSeq::newGetNumFromCode(this.numberSequence_SQ(), false);
salesQuotationTable.QuotationId = num.num();
23
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Performance Guidelines
Performance is a critical piece of the upgrade process and requires that you think about each line in your script.
Most companies will perform this task over a weekend, so the entire upgrade process must be able to be
completed within 48 hours. The actual update will typically be performed between Friday night and Monday
morning. In addition, prior to running the upgrade process on a live system, the upgrade process is tested several
times on a test system.
In addition to the following considerations, please read Performance Improvement Options to determine which
apply to your upgrade scripts:
1. Monitor and minimize the number of client/server calls.
2. Use record set functions whenever possible.
3. Break down your scripts into smaller pieces. For example, do not upgrade two independent tables in
the same script even if there is a pattern in the way the scripts work. This is because:
1. Each script, by default, runs in one transaction (=one rollback segment). If the segment becomes
too large, the database server will start swapping memory to disk, and the script will slowly come
to a halt.
2. Each script can be executed in parallel with other scripts.
1. Partial commits can only be used out of the box in one situation; this is when the table to upgrade is
large and contains a discriminator that can be used to split the script into several scripts. For example,
update all "Open" in one script and all "Closed" in another. The scripts should be set up to be
dependant on each other to avoid locking problems. (see point below regarding database lock
contention)
2. Take care when you sequence the scripts. For example, do not update data first and then delete it
afterwards.
3. Be careful when calling normal business logic in your script. Normal business logic is not usually
optimized for upgrade performance. For example, the same parameter record may be fetched for each
record you need to upgrade. The parameter record is cached, but just calling the Find method takes an
unacceptable amount of time. For example, the kernel overhead for each function call in
Microsoft Dynamics AX is 5 ms. Usually10-15 ms will elapse before the Find method returns (when
the record is cached). If there are a million rows, two hours will be spent getting information you
already have. The solution is to cache whatever is possible in local variables.
4. Run benchmarking on your script using large datasets to verify your performance is acceptable.
5. If database lock contention prevents the data upgrade process from scaling up with multiple batch
clients running in parallel, consider disabling the transaction in the framework and ensuring
idempotency by one of the following:
Using an existing field/condition that can check if the table/record has been updated
Adding new fields to track upgrade status
Using the primary key as ordering columns and recording the last row that was updated
1. Use index tunint. Create indexes to speed up the upgrade and possibly remove them after the upgrade.
Setting up a configuration key to SysDeletedObjects<version> can help you ensure that the index is
deleted after the upgrade is finished.
1. If there is no business logic in the script, rewrite the script to issue a direct query to bulk update the
data. To write Direct SQL queries, see Appendix 2: Guidelines for Writing Direct SQL in Upgrade
Scripts.
24
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Performance Improvement Options
Example:
Before performance improvement:
while select inventTable
where inventTable.ItemType == ItemType::Service
{
this.tableProgress(tablenum(InventTable));
delete_from inventSum where inventSum.ItemId == inventTable.ItemId;
}
update_recordset taxExchRateAdjustment
setting GovernmentExchRate = taxExchRateAdjustment.UseGovtBankRate
where taxExchRateAdjustment.UseGovtBankRate == NoYes::Yes;
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Example:
rilAssetTransMerge = new RecordInsertList(tablenum(assetTransMerge));
while select assetTrans
{
if (!AssetTransMerge::exist(AssetBookType::ValueModel,assetTrans.RecId))
{
assetTransMerge.AssetId = assetTrans.AssetId;
assetTransMerge.AssetGroup = assetTrans.AssetGroup;
…
rilAssetTransMerge.add(assetTransMerge);
}
}
rilAssetTransMerge.insertDatabase();
void someFunc()
{
while select custTable
{
if (custNum != 0)
{
dosomething()
}
}
}
Again, this is not good coding practice. SQL can perfom this operation for you.
Rewrite the above function as:
void someFunc()
{
while select custTable where custNum != 0
{
dosomething()
}
}
26
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Below is another example of wasting CPU cycles in the X++ interpreter:
private ledgerSRUCode somefunc(AccountNum _accountNum)
{
.....
if (auxAccountNum >= '1910' &&
auxAccountNum <= '1979')
{
ledgerSRUCode = '200';
}
return ledgerSRUCode;
}
This function only gets the ledgerSRU. So, when this is done, you should exit the function and not execute the if
statements. Also, if you are aware of the most likely results, test for these most likely options early in your code.
Below is a corrected version:
private ledgerSRUCode someFunc(AccountNum _accountNum)
{
.....
27
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Appendix 1: Guidelines for Writing Direct SQL in Upgrade Scripts
If the update method is overridden, the update_recordset will change into a row-by-row update,
executing the update code for each row. You can prevent this by using the skipDataMethod operator.
Refer to Calling skipDataMethods and skipDatabaseLog Before Calling Update_RecordSet or
Delete_From for more details.
1. An update_recordset or delete_from that includes in its selection criteria a check for existence or
absence of data in the same or different table. In X++ these can be implemented directly using the
EXISTS Join or NOT EXISTS Join.
For example:
while select SalesBasketId from salesBasket
where salesBasket.CustAccount == guestAccount
{
delete_from salesBasketLine
where salesBasketLine.SalesBasketId ==
salesBasket.SalesBasketId;
}
28
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Executing Direct SQL from X++
1. Direct SQL stored procedures are executed using X++ as shown in the following example:
str sql;
str dataAreaId;
Connection conn;
SqlStatementExecutePermission permission;
;
dataAreaId = curExt();
sql = = 'execute <StoredProcName> \' + dataAreaId + '\' \'' + numSeq + '\'';
permission = new SqlStatementExecutePermission(sql);
conn = new Connection();
permission = new SqlStatementExecutePermission(sql);
permission.assert();
conn.createStatement().executeUpdate(sql);
// the permissions needs to be reverted back to original condition.
CodeAccessPermission::revertAssert();
29
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Using Utility Functions to Execute Direct SQL
Two new methods, statementExeUpdate() and statementExeQuery(), have been added to the
ReleaseUpdateDB class. They can be used to run any Direct SQL statements in ReleaseUpdateDB based
classes. Note that, for security reasons, these functions do not have CAS assert() or revertAssert() methods,
these should be called by the caller. See the code example in Stored Procedure and function Guidelines for
ReleaseUpdateDB::statementExeUpdate and ReleaseUpdateDB::statementExeQuery
use.
/*
UPDATE SALESLINE
SET SHIPPINGDATEREQUESTED =
( SELECT MAX(DATEEXPECTED) FROM INVENTTRANS
WHERE INVENTTRANS.DATAAREAID = INVENTTRANS.DATAAREAID
AND SALESLINE.INVENTTRANSID = INVENTTRANS.INVENTTRANSID
AND INVENTTRANS.DATEEXPECTED <> '1900-01-01')
30
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
WHERE SHIPPINGDATEREQUESTED = '1900-01-01'
AND DATAAREAID = SALESLINE.DATAAREAID
AND EXISTS
( SELECT DATEEXPECTED
FROM INVENTTRANS
WHERE INVENTTRANS.DATAAREAID = N'ext'
AND SALESLINE.INVENTTRANSID = INVENTTRANS.INVENTTRANSID
AND INVENTTRANS.DATEEXPECTED <> '1900-01-01')
*/
sqlStmt = 'UPDATE ' + dictTable_SalesLine.name(DbBackend::Sql);
sqlStmt += ' SET ' +
dictTable_SalesLine.fieldName(fieldnum(SalesLine,ShippingDateRequested),DbBackend::Sql);
sqlStmt += ' = ( SELECT MAX(' +
dictTable_InventTrans.fieldName(fieldnum(InventTrans,DateExpected),DbBackend::Sql);
sqlStmt += ') FROM ' + dictTable_InventTrans.name(DbBackend::Sql);
sqlStmt += ' WHERE ' + dictTable_InventTrans.name(DbBackend::Sql);
sqlStmt += '.' +
dictTable_InventTrans.fieldName(fieldnum(InventTrans,DataAreaId),DbBackend::Sql);
sqlStmt += ' = ' + sqlSystem.sqlLiteral(inventTrans.DataAreaId);
sqlStmt += ' AND ' + dictTable_SalesLine.name(DbBackend::Sql);
sqlStmt += '.' +
dictTable_SalesLine.fieldName(fieldnum(SalesLine,InventTransId),DbBackend::Sql);
sqlStmt += ' = ' + dictTable_InventTrans.name(DbBackend::Sql);
sqlStmt += '.' +
dictTable_InventTrans.fieldName(fieldnum(InventTrans,InventTransId),DbBackend::Sql);
sqlStmt += ' AND ' + dictTable_InventTrans.name(DbBackend::Sql);
sqlStmt += '.' +
dictTable_InventTrans.fieldName(fieldnum(InventTrans,DateExpected),DbBackend::Sql);
sqlStmt += ' <> ' + sqlSystem.sqlLiteral('1900-01-01') + ')';
sqlStmt += ' WHERE ' +
dictTable_SalesLine.fieldName(fieldnum(SalesLine,ShippingDateRequested),DbBackend::Sql);
sqlStmt += ' = ' + sqlSystem.sqlLiteral('1900-01-01');
sqlStmt += ' AND ' +
dictTable_SalesLine.fieldName(fieldnum(SalesLine,DataAreaId),DbBackend::Sql);
sqlStmt += ' = ' + sqlSystem.sqlLiteral(salesLine.DataAreaId);
sqlStmt += ' AND EXISTS';
sqlStmt += ' (SELECT ' +
dictTable_InventTrans.fieldName(fieldnum(InventTrans,DateExpected),DbBackend::Sql);
sqlStmt += ' FROM ' + dictTable_InventTrans.name(DbBackend::Sql);
sqlStmt += ' WHERE ' + dictTable_InventTrans.name(DbBackend::Sql);
sqlStmt += '.' +
dictTable_InventTrans.fieldName(fieldnum(InventTrans,DataAreaId),DbBackend::Sql);
sqlStmt += ' = ' + sqlSystem.sqlLiteral(inventTrans.DataAreaId);
sqlStmt += ' AND ' + dictTable_SalesLine.name(DbBackend::Sql);
sqlStmt += '.' +
dictTable_SalesLine.fieldName(fieldnum(SalesLine,InventTransId),DbBackend::Sql);
sqlStmt += ' = ' + dictTable_InventTrans.name(DbBackend::Sql);
sqlStmt += '.' +
dictTable_InventTrans.fieldName(fieldnum(InventTrans,InventTransId),DbBackend::Sql);
sqlStmt += ' AND ' + dictTable_InventTrans.name(DbBackend::Sql);
sqlStmt += '.' +
dictTable_InventTrans.fieldName(fieldnum(InventTrans,DateExpected),DbBackend::Sql);
sqlStmt += ' <> ' + sqlSystem.sqlLiteral('1900-01-01') + ')';
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
WHERE B.DATAAREAID=INVENTSUM.DATAAREAID
AND B.ITEMID=INVENTSUM.ITEMID AND B.ITEMTYPE=2)
In the event that the InventTable is shared among several companies in the „dmo‟ company, then the statement
should be as follows, where the virtual company is assumed to be named „vir‟:
DELETE FROM INVENTSUM
WHERE DATAAREAID=N'dmo' AND
EXISTS (SELECT 'x' FROM INVENTTABLE B
WHERE B.DATAAREAID=N'vir'
AND B.ITEMID=INVENTSUM.ITEMID AND B.ITEMTYPE=2)
To get the correct DataAreaId, declare a table buffer of the specific table type and use the value of the
DataAreaId field in the table buffer.
To get the correct formatting with the „-s and the preceding N, parse the DataAreaId to the
SqlSystem.sqlLiterals method and use the return value.
The following shows the use of DataAreaId and sqlLiteral:
static void UseDataAreaId(Args _args)
{
InventSum inventSum;
InventTable inventTable;
str sqlStr;
SqlSystem sqlSystem = new SqlSystem();
;
sqlStr = strfmt(@"DELETE FROM INVENTSUM
WHERE DATAAREAID=%1 AND
EXISTS (SELECT 'x' FROM INVENTTABLE B
WHERE B.DATAAREAID=%2
AND B.ITEMID=INVENTSUM.ITEMID AND B.ITEMTYPE=2)",
sqlSystem.sqlLiteral(inventSum.dataAreaId),
sqlSystem.sqlLiteral(inventTable.dataAreaId));
}
Notes:
1. The example above assumes that DataAreaId is left justified, which is a valid assumption as it is a
system field where the justification cannot be changed by the customers or partners.
2. The example is only used for demonstrating the use of DataAreaId. The table names and fields should
be retrieved from the dict classes and the statement should be built using name(DbBackend::Sql).
32
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
// table exists in the database.
// isTmp will return true if the table is
// specifically marked as temporary or if it is
// disabled by the configuration key.
}
}
You can test whether a field exists in the database by testing its configuration key as follows:
static void TestField(Args _args)
{
DictField dictField;
;
dictField = new DictField(tableNum(SalesTable),
fieldNum(SalesTable, PriceGroupId));
if (isConfigurationKeyEnabled(dictField.configurationKeyId()))
{
// Field exists in the database
}
}
There is no need to test every field. If you know the field is always in the database because the table is in the
database, then there is no need to test each field individually. You only need to test fields that have a different
configuration key to the table.
33
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Note that if customers have de-selected the option in the Server Configuration utility to not use SUBSTR and
NLS_LOWER, they will not have functional indexes; they will have regular indexes and thus the SUBSTR and
NLS_LOWER is not required.
void runSQLCode()
{
// MS SQL specific code
sqlStmt = 'UPDATE SALESLINE SET DELIVERYADDRESS = T.DELIVERYADDRESS,';
sqlStmt += ' DELIVERYNAME = T.DELIVERYNAME,';
sqlStmt += ' DELIVERYSTREET = T.DELIVERYSTREET,';
sqlStmt += ' DELIVERYZIPCODE = T.DELIVERYZIPCODE,';
sqlStmt += ' DELIVERYCITY = T.DELIVERYCITY,';
sqlStmt += ' DELIVERYCOUNTY = T.DELIVERYCOUNTY,';
sqlStmt += ' DELIVERYSTATE = T.DELIVERYSTATE,';
sqlStmt += ' DELIVERYCOUNTRYREGIONID = T.DELIVERYCOUNTRYREGIONID';
sqlStmt += ' FROM SALESLINE L,';
sqlStmt += ' SALESTABLE T';
sqlStmt += ' WHERE T.DATAAREAID = ' + sqlSystem.sqlLiterals(dataareaid);
sqlStmt += ' AND L.DATAAREAID = T.DATAAREAID';
sqlStmt += ' AND L.SALESID = T.SALESID';
sqlStmt += ' AND L.DELIVERYADDRESS = ' + sqlSystem.sqlLiterals('');
permission = new SqlStatementExecutePermission(sqlStmt);
permission.assert();
statement.ExecuteUpdate(sqlStmt);
}
void runOraCode()
{
// Oracle specific code
}
;
connection = new Connection();
statement = connection.createStatement();
sqlSystem = new SqlSystem();
dataareaid = curExt();
switch (sqlSystem.databaseId())
{
case DatabaseId::MS_Sql_Server :
runSQLCode();
break;
case (DatabaseId::Oracle) :
runOraCode();
break;
default :
break;
}
34
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Implementing Complex Inserts and Updates in Direct SQL
Complex updates cannot be implemented directly in X++. When these conditions are encountered, the update
operations must be rewritten in Direct SQL.
If the method being examined involves one or a small number of update operations, the SQL can be constructed
as a string and executed as described in Executing Direct SQL from X++ in this document.
For more complex methods that operate on multiple tables, it is advisable that the method be rewritten as a
stored procedure. The stored procedure can be executed via X++ as described in Stored Procedure and Function
Guidelines in this document.
void runOraCode()
{
while select vendInvoiceTrans
exists join inventTrans
where inventTrans.InventTransId == vendInvoiceTrans.InventTransId
&& inventTrans.InvoiceId == vendInvoiceTrans.InvoiceId
notexists join dimHistory
where dimHistory.InventTransId == vendInvoiceTrans.InventTransId
&& dimHistory.TransRefId == vendInvoiceTrans.InvoiceId
&& dimHistory.TransactionLogType ==
InventReportDimHistoryLogType::PurchInvoice
{
InventReportDimHistory::addFromVendInvoiceTrans(vendInvoiceTrans);
}
}
;
if (!vendInvoiceTrans.RecId)
35
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
return;
switch(SqlSystem::databaseBackendId())
{
case DatabaseId::Oracle:
runOraCode();
break;
case DatabaseId::MS_Sql_Server:
str_ExecSproc = strfmt(str_SQLEXEC,ReleaseUpdateDB::getSchemaName()
,#CREATEDIMHISTORY_PURCHINVOICE
,sqlSystem.sqlLiteral(vendInvoiceTrans.DataAreaId));
sqlStatementExecutePermission = new
SqlStatementExecutePermission(str_ExecSproc);
sqlStatementExecutePermission.assert();
ReleaseUpdateDB::statementExeUpdate(str_ExecSproc);
CodeAccessPermission::revertAssert();
}
When writing stored procedures that replace X++ methods or functions in the upgrade class, use the following
guidelines:
1. The stored procedure name should be the same as the method or function that it is replacing.
2. The stored procedure should include the original X++ statements as comments to provide context
during testing and troubleshooting.
3. Transactional control statements (BEGIN TRANSACTION, COMMIT) should not be coded in the
stored procedure. Transaction management is implemented in X++.
4. The stored procedure must accept a required parameter of DATAAREAID as data type
NVARCHAR(3).
5. If the stored procedure will be populating a table with a formatted business sequence column
(described in Assigning Business Sequences on Insert section of this document), the procedure must
accept the following parameters:
1. @NUMBERSEQUENCE NVARCHAR(20). This will be used as a key to the
NUMBERSEQUENCE table to retrieve the next key value and format requirements.
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
UPDATE SALESLINE
SET SHIPPINGDATEREQUESTED =
( SELECT MAX(B1.DATEEXPECTED) FROM INVENTTRANS B1
WHERE A.DATAAREAID = B1.DATAAREAID
AND A.DATAAREAID = @dataareaid
AND A.INVENTTRANSID = B1.INVENTTRANSID
AND B1.DATEEXPECTED <> '1900-01-01'
AND A.SHIPPINGDATEREQUESTED = '1900-01-01')
FROM SALESLINE A, INVENTTRANS B0
WHERE A.SHIPPINGDATEREQUESTED = '1900-01-01'
AND A.DATAAREAID = @dataareaid
AND A.INVENTTRANSID = B0.INVENTTRANSID
AND B0.DATEEXPECTED <> '1900-01-01'
update_recordset custPackingSlipTrans
setting DlvCountryRegionId = salesTable.DeliveryCountryRegionId,
DlvCounty = salesTable.DeliveryCounty,
DlvState = salesTable.DeliveryState
where custPackingSlipTrans.SalesId == salesTable.SalesId
&& custPackingSlipTrans.DlvCountryRegionId == '';
}
In this example, the code loops through every SalesTable Entry and:
1. Updates SalesLine with the relevant address information for the salesid.
2. Updates CustInvoicetrans with the address information for salesid.
3. Updates custPackingSlipTrans with the address information for salesid.
Direct SQL needs to be rewritten in this case because of the need to:
1. Perform one mass update where possible.
37
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
2. Reduce looping on a large transactional table such as salesline.
The following is the Transact-SQL code that you should generate from X++:
UPDATE SALESLINE
SET DELIVERYADDRESS = T.DELIVERYADDRESS,
DELIVERYNAME = T.DELIVERYNAME,
DELIVERYSTREET = T.DELIVERYSTREET,
DELIVERYZIPCODE = T.DELIVERYZIPCODE,
DELIVERYCITY = T.DELIVERYCITY,
DELIVERYCOUNTY = T.DELIVERYCOUNTY,
DELIVERYSTATE = T.DELIVERYSTATE,
DELIVERYCOUNTRYREGIONID = T.DELIVERYCOUNTRYREGIONID
FROM SALESLINE L,
SALESTABLE T
WHERE T.DATAAREAID = @DATAAREAID
AND L.DATAAREAID = T.DATAAREAID
AND L.SALESID = T.SALESID
AND L.DELIVERYADDRESS = ''
UPDATE CUSTINVOICETRANS
SET DLVCOUNTRYREGIONID = T.DELIVERYCOUNTRYREGIONID,
DLVCOUNTY = T.DELIVERYCOUNTY,
DLVSTATE = T.DELIVERYSTATE
FROM CUSTINVOICETRANS C,
SALESTABLE T
WHERE T.DATAAREAID = @DATAAREAID
AND C.DATAAREAID = T.DATAAREAID
AND C.SALESID = T.SALESID
AND C.DLVCOUNTRYREGIONID = ''
UPDATE CUSTPACKINGSLIPTRANS
SET DLVCOUNTRYREGIONID = T.DELIVERYCOUNTRYREGIONID,
DLVCOUNTY = T.DELIVERYCOUNTY,
DLVSTATE = T.DELIVERYSTATE
FROM CUSTPACKINGSLIPTRANS C,
SALESTABLE T
WHERE T.DATAAREAID = @DATAAREAID
AND C.DATAAREAID = T.DATAAREAID
AND C.SALESID = T.SALESID
AND C.DLVCOUNTRYREGIONID = ''
The performance improvement achieved in this example is significant. On a database, Baseline ran for 24
minutes. With SET BASED CHANGE, it ran in 16 seconds.
This type of update, which does not require sequencing conditional to each record, can be written in X++ as a
sequence of Direct SQL statements.
38
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Number Sequence Considerations
A complicating factor when we use a Direct SQL set-based insert into a table in the Microsoft Dynamics AX
database is that tables have one or more sequentially assigned numbers which are derived from the
SYSTEMSEQUENCES and NUMBERSEQUENCETABLE tables.
A two-step process of initially populating a temporary table that uses a DBMS-specific sequence mechanism
(IDENTITY for Transact-SQL, ROW NUMBER for Oracle) and then copying the temporary table‟s rows to the
final permanent table is required.
The two sections that follow provide Transact-SQL examples of populating both a system sequence (RECID)
and business sequence.
In all the above scenarios, the allocation is done the same way, using the RECID allocation APIs. There are
three APIs that you need to know about:
RECID suspension - suspendRecids
RECID reservation - reserveValues
RECID releasing suspension - removeRecidSuspension
The APIs are members of the SystemSequence class.
The following is a code snippet of how to use the allocation APIs.
static void Job2(Args _args)
{
SystemSequence s;
AAMyTable t;
int64 startValue;
int i;
;
Create a new instance of the systemSequence class 39
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
s = new SystemSequence();
s.suspendRecIds( tablenum (AAMyTable) ); Suspend the RECID allocation by the kernel
startValue = s.reserveValues( 10, tablenum( AAMyTable ) ); Reserve the RECID by passing in the number of id‟s to
reserve. The return value is the starting value of the
range you reserved. The API gaurantees that the
for ( i = 0; i <10; i++ ) allocated id‟s are contiguous.
{
t.IntFld = i;
t.RecId = startValue + i; Assign the RECID to the RECID column
t.insert();
}
40
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
The abbreviated example below illustrates using SYSTEMSEQUENCES and a temporary table using
IDENTITY for sequential numbers:
CREATE PROCEDURE initFromSMMQuotationTable
@DATAAREAID NVARCHAR(3
AS
DECLARE @NEXTVAL BIGINT, Assign an IDENTITY column with a
@ROWCOUNT BIGINT starting value of 0 incremented by 1
SELECT ......,
RECID = IDENTITY(BIGINT,1,1) AS QUOTATIONID
INTO #TEMP
FROM DEL_SMMQUOTATIONTABLE
WHERE QUOTATIONSTATUS = 0 -- SMMQUOTATIONSTATUS::INPROCESS
Notes:
1. The stored procedure is passed an indicator that specifies if right justification is to take place. A value
of “Y” means right-justify the column. The default is to left-justify the column.
41
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
2. Because formatted sequence columns are of different maximum lengths, you must look up the length of
the column that is to be formatted and record the length in your procedure. The instructions that follow
will describe how you pass the column‟s length, along with the formatting requirements, to a user-
defined SQL function that will format the column correctly.
The example below illustrates the use of a user-defined function FN_FMT_NUMBERSEQUENCE which
accomplishes the formatting and justification requirements of a business sequence column:
CREATE PROCEDURE initFromSMMQuotationTable
@DATAAREAID NVARCHAR(3), You must determine the column‟s
@NUMBERSEQUENCE NVARCHAR(20),
@RJUSTIFY CHAR(1) length if it is to be right justified
and set a variable so we can pass
AS that to the formatting function
DECLARE @NEXTREC BIGINT,
@FORMAT NVARCHAR(40),
@ROWCOUNT BIGINT
@RJUSTIFY_LENGTH INT
In many cases it will be necessary to assign a sequential number both for RECID and a business sequence
column. However, SQL Server only permits one IDENTITY column per table.
The following example demonstrates how to use the single IDENTITY column for both purposes. This example
is also useful as a template for creating new procedures to upgrade data into new tables in the
Microsoft Dynamics AX 4.0 schema:
42
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
CREATE PROCEDURE initFromSMMQuotationTable
@DATAAREAID NVARCHAR(3),
@NUMBERSEQUENCE NVARCHAR(20),
@RJUSTIFY CHAR(1) =’N’
AS
DECLARE @NEXTREC BIGINT,
@NEXTVAL BIGINT,
@FORMAT NVARCHAR(40),
@ROWCOUNT BIGINT
@RJUSTIFY_LENGTH INT
-- Set the length of the column that is to be right-justified
-- Confirm length in table definition
IF RJUSTIFY = ‘Y’
SET @RJUSTIFY_LENGTH = 40
ELSE
SET @RJUSTIFY_LENGTH = 0
-- Insert from temp table to final table. Note that temp table RECID
--is sued to supply values to both QUOTATIONID and REID in final table
INSERT INTO SALESQUOTATIONTABLE
(column-list )
SELECT
DBO.FN_FMT_NUMBERSEQUENCE(@FORMAT, RECID,@NEXTREC, @RJUSTIFY_LENGTH) ,
......,
RECID+@NEXTVAL
FROM #TEMP
43
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND
Calling FN_FMT_NUMBERSEQUENCE
A user defined function FN_FMT_NUMBERSEQUENCE is provided to assist with the formatting
requirements of a business sequence column. This function enables the following operations to be performed:
1. Adds the value of the IDENTITY column to the NEXTREC value retrieved from
NUMBERSEQUENCETABLE.
2. Formats the result according to the FORMAT column retrieved from NUMBERSEQUENCETABLE.
3. Right justifies the formatted column to the length specified. If the function encounters a value of 0, no
justification occurs and the formatted value remains left justified by default.
The parameters that are supplied to FN_FMT_NUMBERSEQUENCE are:
1. The FORMAT column value from NUMBERSEQUENCETABLE.
2. The integer value to be formatted.
3. The value from NEXTREC in NUMBERSEQUENCETABLE. If this is not supplied, it is set to 0 by
default.
The length of the column to be right justified. If this is not supplied it is set to 0 by default. If 0 is
specified or becomes the default, then no justification occurs.
The ReleaseUpdateDB38_Basic::createFnFmtNumberSequence method creates the
FN_FMT_NUBMERSEQUENCE function. If your script needs to call the function, you should make the script
depend on the ReleaseUpdateDB38_Basic::createFnFmtNumberSequence script and then you can reference the
function in your Direct SQL code.
44
HOW TO WRITE DATA UPGRADE SCRIPTS FOR MICROSOFT DYNAMICS AX 4.0 AND BEYOND