Copying existing data is one of the data manipulation tasks in Dynamics 365 for Finance and Operations. There are numerous places in the standard D365 application where users can create new data entries just by copying existing data and then modifying it. A few of the examples are the Copy
button in Cost
management
| Inventory
accounting
| Costing
versions
and the Copy project
button in Project
management
and accounting
| Projects
| All projects
. Also, although the mentioned copying functionality might not be that straightforward, the idea is clear: the existing data is reused while creating new entries.
In this recipe, we will learn two ways to copy records in X++. We will discuss the usage of the table's data()
method, the global buf2buf()
function, and their differences. As an example, we will copy one of the existing ledger account records into a new record.
Carry out the following steps in order to complete this recipe:
- Navigate to
General ledger
|Chart of accounts
|Accounts
|Main accounts
and find the account to be copied. In this example, we will use130100
, as shown in the following screenshot:
- Create a Dynamics 365 for Operations Project, create a runnable class named
MainAccountCopy
with the following code snippet, and run it:
class MainAccountCopy { /// <summary> /// Runs the class with the specified arguments. /// </summary> /// <param name = "_args">The specified arguments.</param> public static void main(Args _args) { MainAccount mainAccount1; MainAccount mainAccount2; mainAccount1 = MainAccount::findByMainAccountId( '130100'); ttsBegin; mainAccount2.data(mainAccount1); mainAccount2.MainAccountId = '130101'; mainAccount2.Name += ' - copy'; if (!mainAccount2.validateWrite()) { throw Exception::Error; } mainAccount2.insert(); ttsCommit; } }
- Navigate to
General ledger
|Chart of accounts
|Accounts
|Main accounts
again and notice that there are two identical records now, as shown in the following screenshot:
In this recipe, we have two variables: mainAccount1
for the original record and mainAccount2
for the new record. First, we find the original record by calling findMainAccountId()
in the MainAccount
table.
Next, we copy it to the new one. Here, we use the data()
table's member
method, which copies all the data fields from one variable to another.
After that, we set a new ledger account number, which is a part of a unique table index.
Finally, we call insert()
on the table if validateWrite()
is successful. In this way, we create a new ledger account record, which is exactly the same as the existing one apart from the account number.
As we saw before, the data()
method copies all the table fields, including system fields such as the record ID, company account, and created user. Most of the time, it is OK because when the new record is saved, the system fields are overwritten with the new values. However, this function may not work for copying records across the companies. In this case, we can use another function called buf2Buf()
. This function is a global function and is located in the Global
class, which you can find by navigating to AOT
| Classes
. The buf2Buf()
function is very similar to the table's data()
method with one major difference. The buf2Buf()
function copies all the data fields excluding the system fields. The code in the function is as follows:
static void buf2Buf( Common _from, Common _to, TableScope _scope = TableScope::CurrentTableOnly) { DictTable dictTable = new DictTable(_from.TableId); FieldId fieldId = dictTable.fieldNext(0, _scope); while (fieldId && ! isSysId(fieldId)) { _to.(fieldId) = _from.(fieldId); fieldId = dictTable.fieldNext(fieldId, _scope); } }
We can clearly see that during the copying process, all the table fields are traversed, but the system fields, such as RecId
or dataAreaId
, are excluded. The isSysId()
helper function is used for this purpose.
In order to use the buf2Buf()
function, the code of the MainAccountCopy
job can be amended as follows:
class MainAccountCopyBuf2Buf { /// <summary> /// Runs the class with the specified arguments. /// </summary> /// <param name = "_args">The specified arguments.</param> public static void main(Args _args) { MainAccount mainAccount1; MainAccount mainAccount2; mainAccount1 = MainAccount::findByMainAccountId('130100'); ttsBegin; buf2Buf(mainAccount1, mainAccount2); mainAccount2.MainAccountId = '130102'; mainAccount2.Name += ' - copy'; if (!mainAccount2.validateWrite()) { throw Exception::Error; } mainAccount2.insert(); ttsCommit; } }