Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
This article outlines the process for devirtualizing company data in Microsoft Dynamics AX 2012 in preparation for an upgrade to Dynamics 365 finance and operations apps.
Important
Devirtualizing data isn't a process that's supported by Microsoft Support. The information provided in this document is intended solely for informational and guidance purposes.
Background
Virtual company accounts were deprecated in finance and operations apps. The virtual companies feature was replaced by cross-company data sharing. Learn more:
How virtual companies work in AX 2012
To understand how virtual company accounts affect an upgrade, you must first learn how they work in AX 2012.
Company data uses the DataAreaId
field for tables where the Save Data Per Company property is enabled. Here is an example:
- There are three companies: CMP1, CMP2, and CMP3.
- A virtual company account that is named VIR is created.
- Companies CMP2 and CMP3 are assigned to virtual company account VIR, together with a table collection that contains the
CustGroup
andVendGroup
tables.
The following table shows the DataAreaId
value that data for each company is stored under.
Company | DataAreaId |
---|---|
CMP1 | CMP1 |
CMP2 | VIR |
CMP3 | VIR |
Because the data for companies CMP2 and CMP3 is stored under the DataAreaId
value of virtual company account VIR, their records are shared.
SQL behavior in AX 2012 vs. Dynamics 365
When a SQL query runs, the AX 2012 kernel swaps the actual company ID for the virtual ID in the WHERE
clause. This translation enables the data to be returned from the tables. The following examples show SQL SELECT
statements for companies CMP1, CMP2, and CMP3 in AX 2012.
Company CMP1
SELECT T1.CUSTGROUP, T1.NAME, T1.CREDITMAX, T1.BLOCKED, T1.DATAAREAID, T1.PARTITION
FROM CUSTGROUP T1
WHERE T1.DATAAREAID = 'CMP1'
AND T1.PARTITION = 5637144576
Companies CMP2 and CMP3
SELECT T1.CUSTGROUP, T1.NAME, T1. TAXGROUPID, T1.DATAAREAID, T1.PARTITION
FROM CUSTGROUP T1
WHERE T1.DATAAREAID = 'VIR'
AND T1.PARTITION = 5637144576
In Dynamics 365, this translation doesn't occur. The following example shows the query for company CMP2 in Dynamics 365.
SELECT T1.CUSTGROUP, T1.NAME, T1. TAXGROUPID, T1.DATAAREAID, T1.PARTITION
FROM CUSTGROUP T1
WHERE T1.DATAAREAID = 'CMP2'
AND T1.PARTITION = 5637144576
Because no records have a DataAreaId
value of CMP2
, this query returns nothing. Therefore, data is missing after the upgrade unless you first devirtualize the data.
Devirtualize data
Data devirtualization is a complex process and depends on the original virtual company setup. If you're unfamiliar with the concepts or tools that are involved, consider working with a qualified professional.
The following steps are general guidance, not a complete, end-to-end solution. The exact process can vary, depending on your table collection setup. This setup includes the structure, relationships, and data volume. All these aspects can vary between implementations.
Important
Always back up your data before you start. After devirtualization is completed, test thoroughly to confirm data integrity.
Step 1: Determine the virtual company setup
Review the virtual company configuration:
- Use the Application Object Tree (AOT) to identify tables in table collections.
- Use the Virtual Company form to determine which companies are linked to which collections.
Step 2: Look for relationships to the RecId value
Determine whether each table that you identified in step 1 has a foreign key that references the RecId
value of the virtualized table.
For example, if the CustomsTariffCodeTable_IN
table was virtualized, use the AX 2012 Cross-reference Tool to find related tables.
In this case, you find that the TaxData
table has the following relation:
TaxData.CustomsTariffCodeTable_IN = CustomsTariffCodeTable_IN.RecId
This relation indicates that the TaxData
table references the RecId
value of the virtualized table.
Step 3: Look for hidden relationships
In addition to direct table relations that are defined in the AOT, there might be references that are based on table IDs. These references typically use a pair of fields such as RefTableId
and RefRecId
. (The exact field names can vary.) The combination of a table ID and a RecId
value establishes the relationship.
The following SQL query helps you find tables of this type and returns only tables that have records.
SELECT T1.TABLE_NAME, T1.COLUMN_NAME, T2.COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS T1
JOIN INFORMATION_SCHEMA.COLUMNS T2
ON T1.TABLE_NAME = T2.TABLE_NAME
JOIN sys.tables st
ON st.name = T1.TABLE_NAME
JOIN sys.partitions sp
ON st.object_id = sp.object_id
WHERE T1.COLUMN_NAME LIKE '%RefTableId%'
AND T2.COLUMN_NAME LIKE '%RefRecId%'
AND sp.index_id IN (0, 1) -- 0 = heap, 1 = clustered index
AND sp.rows > 0 -- Only return tables with data
GROUP BY T1.TABLE_NAME, T1.COLUMN_NAME, T2.COLUMN_NAME
ORDER BY T1.TABLE_NAME;
Note
This script finds most references. However, be sure to review the results against any system customizations or changes.
After you run the query, check whether any of the tables that are returned reference tables in the virtual company setup. Get the table IDs from the AOT. Track any tables that reference virtualized tables.
Step 4: Look for other fields
Check the data in the virtualized tables for any extra fields that can reference the virtual company ID.
For example, the CUSTQUOTATIONTRANS
table includes a field that is named COMPANY
. If the values in this field match the virtual company ID, document this match, and include it in the devirtualization process.
Although these references aren't always part of formal table relationships, they can affect how you interpret data. Therefore, handle them carefully.
Step 5: Copy and create records
Use the following script to copy data from the virtual company to individual legal entities.
Before you run the script, change the following values as required:
@TableName
@VirtualCompany
@CompanyList
-- Edit following three values as needed
DECLARE @TableName NVARCHAR(128) = 'CUSTGROUP'; -- Your table name
DECLARE @VirtualCompany NVARCHAR(10) = 'vir'; -- Virtual company (e.g., 'vir')
DECLARE @CompanyList NVARCHAR(MAX) = ' cmp2, cmp3'; -- List of companies to insert records for
-- Script Start
DECLARE @ColumnList NVARCHAR(MAX);
DECLARE @MaxRecId BIGINT;
DECLARE @SQL NVARCHAR(MAX);
DECLARE @Company NVARCHAR(50);
DECLARE @CurrentMaxRecId BIGINT;
DECLARE @xml XML;
DECLARE @IndexName NVARCHAR(128);
DECLARE @TabId INT;
DECLARE @NextVal BIGINT;
DECLARE @NewMaxRecId BIGINT;
SET @IndexName = 'DEVIRTUALIZEIDX_' + @TableName;
IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = @TableName AND COLUMN_NAME = 'OLDRECID')
BEGIN
SET @SQL = 'ALTER TABLE ' + QUOTENAME(@TableName) + ' ADD OLDRECID BIGINT;';
EXEC sp_executesql @SQL;
END
SELECT @ColumnList = STUFF((SELECT ', ' + COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = @TableName
AND COLUMN_NAME NOT IN ('DATAAREAID', 'RECID', 'OLDRECID')
FOR XML PATH('')), 1, 2, '');
SET @CompanyList = REPLACE(@CompanyList, ' ', '');
SET @xml = (SELECT CAST('<cr>' + REPLACE(@CompanyList, ',', '</cr><cr>') + '</cr>' AS XML) AS STRING);
DECLARE CompanyCursor CURSOR FOR
SELECT t.value('.', 'varchar(max)') AS COMPANYLIST FROM @xml.nodes('//cr') AS a(t);
OPEN CompanyCursor;
FETCH NEXT FROM CompanyCursor INTO @Company;
WHILE @@FETCH_STATUS = 0
BEGIN
SET @SQL = 'SELECT @MaxRecId = MAX(RECID) FROM ' + QUOTENAME(@TableName) + ';';
EXEC sp_executesql @SQL, N'@MaxRecId BIGINT OUTPUT', @MaxRecId OUTPUT;
SET @SQL = 'INSERT INTO ' + QUOTENAME(@TableName) + ' (' + @ColumnList + ', DATAAREAID, RECID, OLDRECID) ' +
'SELECT ' + @ColumnList + ', ''' + @Company + ''', ' +
'ROW_NUMBER() OVER (ORDER BY RECID) + ' + CAST(@MaxRecId AS NVARCHAR) + ', ' +
'RECID ' + -- Populate OldRecId with the existing RecId
'FROM ' + QUOTENAME(@TableName) + ' ' +
'WHERE DATAAREAID = ''' + @VirtualCompany + ''';';
EXEC sp_executesql @SQL, N'@VirtualCompany NVARCHAR(10)', @VirtualCompany;
FETCH NEXT FROM CompanyCursor INTO @Company;
END;
CLOSE CompanyCursor;
DEALLOCATE CompanyCursor;
-- Optional - add in an index for field OLDRECID, used when updating foreign relations.
-- Comment out if not required
IF NOT EXISTS (SELECT 1 FROM sys.indexes WHERE name = @IndexName AND object_id = OBJECT_ID(@TableName))
BEGIN
SET @SQL = 'CREATE INDEX ' + QUOTENAME(@IndexName) + ' ON ' + QUOTENAME(@TableName) + ' (OLDRECID, RECID, DATAAREAID);';
EXEC sp_executesql @SQL;
END
-- End of OLDRECID index section
-- Update SYSTEMSEQUENCES
SELECT @TabId = TABLEID FROM SQLDICTIONARY
WHERE SQLNAME = @TableName AND FIELDID = 0 AND ARRAY = 0;
SET @SQL = 'SELECT @NewMaxRecId = ISNULL(MAX(RECID), 0) FROM ' + QUOTENAME(@TableName) + ';';
EXEC sp_executesql @SQL, N'@NewMaxRecId BIGINT OUTPUT', @NewMaxRecId OUTPUT;
SELECT @NextVal = NEXTVAL FROM SYSTEMSEQUENCES
WHERE TABID = @TabId;
IF @NextVal IS NOT NULL AND @NextVal < @NewMaxRecId + 1
BEGIN
UPDATE SYSTEMSEQUENCES
SET NEXTVAL = @NewMaxRecId + 1
WHERE TABID = @TabId;
END
-- End Of Script
Step 6: Update relationships
After you devirtualize the data in a table, update any related table references that point to it. As was noted earlier, these references are often fields such as RefRecId
. However, the field names can vary.
To maintain referential integrity, update these related fields with the new RecId
values. Use the corresponding OLDRECID
value from the devirtualized table as a reference.
The following example shows how to update table references.
UPDATE T2
SET T2.REFRECID = T1.RECID
FROM TABLE2 AS T2
JOIN TABLE1 AS T1
ON T1.OLDRECID = T2.REFRECID
AND T1.DATAAREAID = T2.DATAAREAID
AND T1.PARTITION = T2.PARTITION
WHERE T2.DATAAREAID IN ('cmp1', 'cmp2')
Step 7: Remove virtualized records
After the data is successfully devirtualized, you can remove the original records that are associated with the virtual company. You must use SQL to do this cleanup.
The following example shows how to delete the records.
--Edit TABLE and PARTITIONKEY as needed.
DELETE FROM TABLE
WHERE DATAAREAID = 'vir'
AND PARTITION = (SELECT RECID FROM PARTITIONS WHERE PARTITIONKEY = 'initial')
If you didn't comment out the part of the devirtualization script that creates an index on the OLDRECID
field, drop that index after cleanup to avoid leaving unnecessary objects in the database.
Important
To prevent unintended data loss, always review the records before you delete them.
Step 8: Clean up virtualized companies
Finally, use the Virtual Company setup form in AX 2012 to delete virtual company definitions.
Learn more in Delete a virtual company account.
Note
Although this article assumes devirtualization in AX 2012, you can devirtualize data after you restore the AX database as AXDB in a cloud-hosted environment (CHE), or in the sandbox just before you start the upgrade (step 10 of the Data migration toolkit).