More prettifications

This commit is contained in:
waldo1001 2021-11-05 13:52:12 +01:00
parent 71e12a44a4
commit 7ff6ff73fa
11 changed files with 248 additions and 249 deletions

View file

@ -2,17 +2,17 @@
title = "Argument Table"
weight = 220
+++
# Argument table pattern
## Argument table pattern
_Originally By Nikola Kukrika and Waldo_
_Originally By Nikola Kukrika and waldo_
# Abstract
### Abstract
The Argument Table pattern is used to provide an extension point for adding new arguments without changing the signature. By grouping multiple arguments into a table the code becomes more readable (function signature and the usage of the function).
[![ ][image0]][anchor0]
# Problem
### Problem
In CAL overloading function signature is not supported. It is also not possible to provide default values for the function arguments.
@ -24,7 +24,7 @@ The last problem that can be solved is high number of arguments. Functions with
Few examples of the bad implementations are as illustrated here:
## Bad example 1
#### Bad example 1
```AL
@ -40,7 +40,7 @@ FillInVATReturnData(NoSeries, NextLineID, CustomerID, DocumentNo, SingleCopy, ??
In this example the code is hard to read and understand. Adding an additional argument will require refactoring of the existing function. Each time a new argument is added a new function will be created.
## Bad example 2
#### Bad example 2
```AL
LOCAL PROCEDURE GetTableSyncSetupW1@3(OldTableId@1002 : Integer; VAR UpgradeTableId@1001 : Integer; VAR TableUpgradeMode@1000 : 'Check, Copy, Move, Force') : Boolean;
@ -61,7 +61,7 @@ END;
In this example each time a new argument is added all function calls will have to be updated. Option is duplicated in the signature, which will cause issues if a new option is defined or the existing options are renamed.
# Solution
### Solution
By grouping the arguments within the table it is possible to add additional argument and reuse it where it is needed without changing the signature.
@ -73,7 +73,7 @@ Argument table should preferably be a temporary table since the implementation i
The examples of usages addressing problems shown above are:
## Good example 1
#### Good example 1
New table
```AL
@ -92,7 +92,7 @@ FillInVATReturnData(VATReturnData);
By introducing an argument table, code is much more readable since there is a single argument for a function. It is easy to see which arguments are passed in and which are modified in a function.
## Good example 2
#### Good example 2
Good example
```AL
@ -106,17 +106,17 @@ END;
Option definition is not encapsulated within the table. Arguments are grouped and we can add additional arguments without the need to change the signature.
# Downsides
### Downsides
You need to create one more table
Complex types can't be embedded as fields in tables (cannot have a record field type etc).
# NAV Usages
### NAV Usages
Upgrade Codeunits
# Related Patterns
### Related Patterns
Posting Routine, Select behavior: Setting fields on existing records in order not to change the signatures.

View file

@ -4,11 +4,11 @@ weight = 470
+++
_Written by Bogdan Andrei Sturzoiu, at Microsoft Development Center Copenhagen_
**Abstract**
## Abstract
This pattern implements a generic mechanism for dynamically restricting and allowing usage of a record by the business process administrator.
**Problem**
## Problem
A NAV record can be used in a number of functionalities across the app. There are situations, however, when the administrator wants to restrict the consumption of such a record, as well as lift the restriction when it is no longer relevant.
@ -23,7 +23,7 @@ The blocked entity pattern involves:
In contrast, the Data-driven Blocked Entity pattern involves adding a new record (data change) to mark the restriction, instead of adding a new field (metadata change).
**Solution**
## Solution
This pattern describes a generic mechanism of adding and lifting restrictions for any type of record.
@ -40,9 +40,9 @@ Currently, the restrictions are record-based and type-less. They act as simple t
You must make sure to differentiate between the reason and the purpose. That is because the restriction can only be added once per record, but consumed in multiple places.
****
**Example**
## Example
For example, we want to restrict posting Gen. Journal Lines if a customer has not been added in Account No. field.
@ -52,7 +52,7 @@ For this, the following components are needed:
2. When you validate a Customer No. as Account no. and Customer as Account Type, lift the restrictions by calling AllowRecordUsage in COD1550\.
3. The consumption of the restriction at posting is already implemented as an event in TAB81, OnCheckGenJournalLinePostRestrictions. No further action necessary.
**NAV Usage**
## NAV Usage
All the approval workflows include a response that restricts usage of a record, and then, at the end of an approval loop, a response that allows the usage again by lifting the restriction. See responses "Add record restriction" and "Remove record restriction" implemented in COD1521\.[
][anchor2]
@ -88,15 +88,15 @@ END;
Notice how lifting a restriction for a Gen. Journal Batch involves lifting all the restrictions for the individual journal lines in the batch (hence the special branching of the code).
****
**Consequences**
## Consequences
Currently, there can only be one restriction per record. There are no restriction types.
In the future, a type field should be added to the restriction table, to allow adding restrictions for different purposes, and to refine their consumption. For example, a posting restriction might only be enforced for restrictions originating from approvals.
**NAV Versions**
## NAV Versions
This pattern has been introduced in Dynamics NAV 2016\.

View file

@ -4,21 +4,21 @@ weight = 370
+++
_Originally by Jan Hoek at IDYN _
## **Abstract**
## Abstract
The Conditional Cascading Update pattern is used to intelligently populate fields whose values depend on other field values. In this pattern description, the field triggering the update will be called "source field", and the depending field will be called "target field".
## **Description**
## Description
The value of one table field sometimes depends on the value of another field, typically following an application-defined transformation (note that we're talking about transformations of field values here. This has nothing to do with e.g. form transformation), such as conversion to uppercase, removal of certain characters etc.
If the target field is non-editable, said transformation is usually the only way for the target field to receive new values, so no irreproducible information can be lost. However, if the target field is editable, the user may have cared enough to override the default (transformed) value, in which case revalidating the source field should not blindly replace the target field's value.
## **Usage**
## Usage
In the OnValidate trigger of the source field, test if the target field value is either blank, or equal to the transformed value of the source field's previous contents. If it is, populate the target field's value with the transformed source field value. If it is not, do nothing (effectively preserving the value set by the user).
## **NAV Specific Example**
## NAV Specific Example
In the base application, this pattern can be found in Search Name/Search Description fields, which are updated with the uppercase value from the corresponding Name/Description field when the latter is validated, only if the Search Name/Description in question is currently blank, or equal to the (uppercase equivalent) of the previous contents of the Name/Description field.
@ -26,7 +26,7 @@ In the base application, this pattern can be found in Search Name/Search Descrip
In this particular case, the transformation between source and target fields is implicit and due to the different data types of the fields (text vs. code). Note how the field triggers of the Search Name field itself do not contain any logic linked to this pattern.
## **Consequences**
## Consequences
There is a case when this pattern should not be used. If the target field is non-editable, this pattern will not add any value, since there won't be any user-overridden values to protect.

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

View file

@ -50,6 +50,7 @@ The Copy Document pattern involves the following entities:
* Source Document No.
* Include Header (optional)
* Recalculate Lines (optional)
Example: REP901, Copy Assembly Document
[![ ][image0]][anchor0]
@ -103,16 +104,16 @@ In the standard version of Microsoft Dynamics NAV, the Copy Document functionali
[anchor0]: /cfs-file/__key/communityserver-wikis-components-files/00-00-00-00-42/clip_5F00_image002.gif
[anchor1]: /cfs-file/__key/communityserver-wikis-components-files/00-00-00-00-42/clip_5F00_image004.gif
[anchor0]: clip_image002.gif-750x0.png
[anchor1]: clip_image004.gif-750x0.png
[anchor2]: clip_5F00_image006.jpg
[anchor3]: clip_5F00_image008.jpg
[anchor4]: clip_5F00_image010.jpg
[anchor5]: https://www.youtube.com/watch?v=aTiwroXwW_0&list=PLhZ3P-LY7CqmVszuvtJLujFyHpsVN0U_w&index=17
[image0]: /resized-image/__size/750x0/__key/communityserver-wikis-components-files/00-00-00-00-42/clip_5F00_image002.gif
[image1]: /resized-image/__size/750x0/__key/communityserver-wikis-components-files/00-00-00-00-42/clip_5F00_image004.gif
[image0]: clip_image002.gif-750x0.png
[image1]: clip_image004.gif-750x0.png
[image2]: clip_5F00_image006.jpg
[image3]: clip_5F00_image008.jpg
[image4]: clip_5F00_image010.jpg

View file

@ -2,22 +2,22 @@
title = "Cross Session Events"
weight = 430
+++
#### _By Nikolai L'Estrange, from TVision Technology Ltd. in the UK__
_
### Abstract
_By Nikolai L'Estrange, from TVision Technology Ltd. in the UK_
## Abstract
Track things that happen in other NAV Sessions.
[![ ][image0]][anchor0]
### Problem
## Problem
In Microsoft Dynamics NAV you can fire a function whenever something changes within your session (and from NAV 2016 this is even easier with the new Event model), however there is not an easy way to know what is happening in other sessions. Sometimes you would like to know what has happened since your last read, without reading everything again, e.g. when you need to pass a large dataset to a Control Add-in.
A common way of handling this with Ledger Tables is to make note of the last record you read, and continuously poll to see if there are any new records. However this is restricted to strictly sequentially entered tables.
### Solution
## Solution
There is a common pattern in many other languages called [Publish-Subscribe][anchor1] (or PubSub) that solves the same issue. We can implement the same pattern in NAV using a Table as a message queue platform and polling this table. We have named this pattern "Cross Session Events" in order to avoid confusion with the standard NAV Events which use the terms Publisher and Subscriber, and to try and describe more accurately when you would need this pattern.
@ -28,25 +28,26 @@ The pattern has four components:
* **Message Broker**: This distributes all messages sent in to all Subscribers that have expressed an interest (i.e. the message is within their filters).
* **Message Queue**: To hold the messages for each Subscriber. Generally once these messages are read, they are deleted.
### Example
## Example
An example of this would be when we have multiple users looking at the same set of data and we want their screens to update in "real time" whenever one of them makes a change, without doing a full refresh. We will use the [Observer pattern][anchor2] to capture the change (act as the Publisher) and then create a Table to hold Subscribers and Filters (Change Observer), a Table to be the Message Queue (Change Notification), and a Codeunit to be the Message Broker and help with the polling (ObserverMgt).
Below are the table definitions:
**Change Observer:
**"Table ID" Integer "Observable Table"
```al
//Change Observer:
//"Table ID" Integer "Observable Table"
"Server ID" Integer
"Session ID" Integer
**Change Notification:
**"Table ID" Integer "Observable Table"
//Change Notification:
//"Table ID" Integer "Observable Table"
"Server ID" Integer
"Session ID" Integer
"Entry No." Integer AutoIncrement
"Type of Change" Option Insert,Modify,Delete,Rename
"Record ID" RecordID
... (other fields to indicate what has changed)
```
The Change Observer table identifies the Subscriber using Server ID and Session ID, and then in this example there is only one filter, which is the Table ID we want to listen to any changes. In this case all three fields are in the Primary Key.
@ -56,83 +57,93 @@ The Change Notification table then has the same three fields plus an Entry No. a
_**Note:** _Other examples of the pattern could have very different fields to identify the Subscriber, Filters and then whatever fields needed for content of the Message.
Our Message Broker Codeunit will also serve as a central place to create Subscribers (Listen and StopListening functions) and a place to Poll for Messages. Note that the Poll function deletes the Messages as it reads them.
```al
//Listen(TableID : Integer)**
WITH Observer DO BEGIN
"Table ID" := TableID;
"Server ID" := SERVICEINSTANCEID;
"Session ID" := SESSIONID;
INSERT(TRUE);
COMMIT;
END;
**Listen(TableID : Integer)**
//StopListening(TableID : Integer)
WITH Observer DO BEGIN
"Table ID" := TableID;
"Server ID" := SERVICEINSTANCEID;
"Session ID" := SESSIONID;
INSERT(TRUE);
COMMIT;
RESET;
SETRANGE("Server ID",SERVICEINSTANCEID);
SETRANGE("Session ID",SESSIONID);
SETRANGE("Table ID",TableID);
DELETEALL(TRUE);
COMMIT;
END;
**StopListening(TableID : Integer)
**WITH Observer DO BEGIN
RESET;
SETRANGE("Server ID",SERVICEINSTANCEID);
SETRANGE("Session ID",SESSIONID);
SETRANGE("Table ID",TableID);
DELETEALL(TRUE);
COMMIT;
END;
**NotifyAll(ChangeNotification : Record "Change Notification")**
//NotifyAll(ChangeNotification : Record "Change Notification")**
WITH Observer DO BEGIN
RESET;
SETRANGE("Table ID",ChangeNotification."Table ID");
IF FINDSET THEN REPEAT
Notify(Observer,ChangeNotification);
UNTIL NEXT = 0;
RESET;
SETRANGE("Table ID",ChangeNotification."Table ID");
IF FINDSET THEN REPEAT
Notify(Observer,ChangeNotification);
UNTIL NEXT = 0;
END;
**Notify(Observer : Record "Change Observer";ChangeNotification : Record "Change Notification")**
//Notify(Observer : Record "Change Observer";ChangeNotification : Record "Change Notification")**
WITH ChangeNotification DO BEGIN
"Server ID" := Observer."Server ID";
"Session ID" := Observer."Session ID";
"Entry No." := 0;
INSERT;
"Server ID" := Observer."Server ID";
"Session ID" := Observer."Session ID";
"Entry No." := 0;
INSERT;
END;
**Poll(TableID : Integer;VAR TempChangeNotification : TEMPORARY Record "Change Notification")**
//Poll(TableID : Integer;VAR TempChangeNotification : TEMPORARY Record "Change Notification")**
WITH ChangeNotification DO BEGIN
TempChangeNotification.RESET;
TempChangeNotification.DELETEALL;
RESET;
SETRANGE("Table ID",TableID);
SETRANGE("Server ID",SERVICEINSTANCEID);
SETRANGE("Session ID",SESSIONID);
IF FINDSET THEN REPEAT
TempChangeNotification := ChangeNotification;
TempChangeNotification.INSERT;
MARK(TRUE);
UNTIL NEXT = 0;
MARKEDONLY(TRUE);
DELETEALL;
TempChangeNotification.RESET;
TempChangeNotification.DELETEALL;
RESET;
SETRANGE("Table ID",TableID);
SETRANGE("Server ID",SERVICEINSTANCEID);
SETRANGE("Session ID",SESSIONID);
IF FINDSET THEN REPEAT
TempChangeNotification := ChangeNotification;
TempChangeNotification.INSERT;
MARK(TRUE);
UNTIL NEXT = 0;
MARKEDONLY(TRUE);
DELETEALL;
END;
```
**__**The final part of this example is an object that calls the functions above. In this example we will use a Page with a PingPong Timer Control to do the polling in (almost) real time. These are the functions on the page:
**OnQueryClosePage(CloseAction : Action None) : Boolean**
```al
//OnQueryClosePage(CloseAction : Action None) : Boolean**
ObserverMgt.StopListening(DATABASE::"NAV Whiteboard Booking");
**Timer::AddInReady()**
//Timer::AddInReady()**
IF ObserverMgt.Listen(DATABASE::"NAV Whiteboard Booking") THEN
CurrPage.Timer.Ping(1000);
**Timer::Pong()**
//Timer::Pong()**
CallUpdate;
CurrPage.Timer.Ping(1000);
**LOCAL CallUpdate()**
//LOCAL CallUpdate()**
ObserverMgt.Poll(DATABASE::"NAV Whiteboard Booking",TempChangeNotification);
WITH TempChangeNotification DO BEGIN
IF FINDSET THEN REPEAT
IF "Type of Change" = "Type of Change"::Delete THEN BEGIN
...
END ELSE IF RecRef.GET("Record ID") THEN BEGIN
...
END;
UNTIL NEXT = 0;
IF FINDSET THEN REPEAT
IF "Type of Change" = "Type of Change"::Delete THEN BEGIN
...
END ELSE IF RecRef.GET("Record ID") THEN BEGIN
...
END;
UNTIL NEXT = 0;
END;
```
### Consequences
## Consequences
The PingPong control is only available on the Windows Client, so if you want to use another client you will need to use another solution to Poll for Messages. Therefore this pattern is not always going to be "real time".
### Related Topics
## Related Topics
This pattern was originally described in the following blog:

View file

@ -13,12 +13,12 @@ A side effect is reduced and simplified code, increased performance and a more s
There is no way in NAV to get a set of records from the database, which all have the newest starting date, that is less than or equal to today's date. Having an ending date on the record will help, but it introduces some other problems. In Dynamics NAV this is normally done by reading too many records, either at the SQL Server level or in the middle tier and throw away the ones you do not need. That is a waste of resources:
< --\[if supportLists\]--\>\* < --\[endif\]--\>The SQL Server is reading too many records
* The SQL Server is reading too many records
< --\[if supportLists\]--\>\* < --\[endif\]--\>There would be too much data sent over the network.
* There would be too much data sent over the network.
(If the SQL Server and the NAV Service tier are on different machines.)
< --\[if supportLists\]--\>\* < --\[endif\]--\>The NAV Service Tier receives and throws away data.
* The NAV Service Tier receives and throws away data.
### Ending Date Problem
@ -38,99 +38,85 @@ Use the pattern whenever you read a set of data containing a Starting Date and y
In the following example, we have a fictive table containing: Code, Starting Date and Price. The Primary Key consist of Code, Starting Date. The Database is the Demo Database, and the Company is Cronus.
< --\[if supportLineBreakNewLine\]--\>[![ ][image0]][anchor0]
< --\[endif\]--\>
[![ ][image0]][anchor0]
### 1\. Create the view
### 1. Create the view
You will need to create the view before you define the Table Object.
You will need to create a view for every company in the database.
CREATE VIEW \[dbo\].\[CRONUS$PriceView\]
AS
SELECT \[Code\], \[Starting Date\], \[Price\]
FROM dbo.\[CRONUS$Price\] AS A
WHERE \[Starting Date\] =
(SELECT MAX(\[Starting Date\])
FROM dbo.\[CRONUS$Price\] AS B
WHERE B.\[Code\] = A.\[Code\] AND
B.\[Starting Date\] <= GETDATE())
```sql
CREATE VIEW [dbo].[CRONUS$PriceView]
AS
SELECT [Code], [Starting Date], [Price]
FROM dbo.[CRONUS$Price] AS A
WHERE [Starting Date] =
(SELECT MAX([Starting Date])
FROM dbo.[CRONUS$Price] AS B
WHERE B.[Code] = A.[Code] AND
B.[Starting Date] <= GETDATE())
```
Test the view to ensure that you get the correct result. It is much easier to test now than later.
### 2\. Create the Table object
### 2. Create the Table object
Remember to set the link table property before you save it.
### 3\. Implement the code
### 3. Implement the code
IF PriceView.FINDSET THEN // You have them
### 4\. Create a deployment codeunit
### 4. Create a deployment codeunit
Create a SQL Deployment codeunit to manage your views.
The codeunit needs to Create or Alter the views for all companies.
To see an example of how to talk to SQL Server using .NET see Waldo's blog here:
To see an example of how to talk to SQL Server using .NET see waldo's blog here:
[http://dynamicsuser.net/blogs/waldo/archive/2011/07/19/net-interop-calling-stored-procedures-on-sql-server-example-1.aspx][anchor1]
< --\[if supportLineBreakNewLine\]--\>
< --\[endif\]--\>
### 5\. Deployment
### 5. Deployment
You need to deploy in three steps:
< --\[if supportLists\]--\>1\. < --\[endif\]--\>Delete the table objects referencing the views
< --\[if supportLists\]--\>2\. < --\[endif\]--\>Deploy and run the deployment codeunit
< --\[if supportLists\]--\>3\. < --\[endif\]--\>Deploy the new table objects that reference the views
1. Delete the table objects referencing the views
2. Deploy and run the deployment codeunit
3. Deploy the new table objects that reference the views
### General precaution
If you later want to change the view, you need to follow these rules:
< --\[if supportLists\]--\>\* < --\[endif\]--\>If you add columns, you need to add them to the view first and then add them to the Table Object.
< --\[if supportLists\]--\>\* < --\[endif\]--\>If you want to remove columns from the view, you need to delete the Table Object, then change the view and last recreate the Table Object without the new columns.
\* If you add columns, you need to add them to the view first and then add them to the Table Object.
\* If you want to remove columns from the view, you need to delete the Table Object, then change the view and last recreate the Table Object without the new columns.
### Code example that accomplish the same but without using the pattern
This following example will give you the same result but the performance will deteriorate as time goes by and you get more and more old data.
```al
Price.SETCURRENTKEY(Code,"Starting Date");
Price.SETFILTER("Starting Date",'..%1', TODAY
IF Price.FINDSET THEN BEGIN
REPEAT
Price.SETRANGE(Code, Price.Code);
Price.FINDLAST;
Price.SETRANGE(Code);
PriceTemp := Price;
PriceTemp.INSERT;
UNTIL Price.NEXT = 0;
REPEAT
Price.SETRANGE(Code, Price.Code);
Price.FINDLAST;
Price.SETRANGE(Code);
PriceTemp := Price;
PriceTemp.INSERT;
UNTIL Price.NEXT = 0;
END;
// PriceTemp will contain the Prices
```
### Comparison
The above NAV example is for a very simple date controlled solution and is provided to give an idea of what the pattern changes seen from a NAV development point of view. But consider the following:
< --\[if supportLists\]--\>\* < --\[endif\]--\>The table has a more complex key.
* The table has a more complex key.
This will require setting and clearing more filters
< --\[if supportLists\]--\>\* < --\[endif\]--\>You need to read from more than one table.
* You need to read from more than one table.
Say you need to apply discount from a separate table.
This may give several lines in PriceTemp.
< --\[if supportLists\]--\>\* < --\[endif\]--\>If the Code field is controlled by a Type field.
* If the Code field is controlled by a Type field.
The Code field reference keys in different tables
All three examples above can be implemented directly in the view. By using the pattern, it will still only require a single line of NAV code.

View file

@ -8,15 +8,15 @@ _By David Bastide and Soumya Dutta at Microsoft Development Center Copenhagen_
[![ ][image0]][anchor0]
## **Context:**
## Context:
This pattern is describing how you can migrate data using the Data Migration Façade.
## **Problem:**
## Problem:
Writing migration code from an external source, such as a product from a competitor, can be time consuming, as you need to tackle the problems of what to migrate, in which order, exception handling... and can result in code that is fragile due to deep dependencies on the NAV data model (high coupling). Any change to the NAV data model can easily break this code. ****
## **Solution:**
## Solution:
The idea of the Data Migration Façade is to provide an API that creates and updates master data and related entities (including transactions) without referencing NAV tables.
@ -39,7 +39,7 @@ The façade framework has the following components:
* * **Data Migration Overview** (page 1799)
## **Usage:**
## Usage:
There are the following use cases:
@ -55,7 +55,7 @@ This deletes existing status lines for migrating Items for 'My Migration Type',
**"Data Migration Façade".StartMigration('My Migration Type',false) **
This starts the migration. False means this is not a retry. A re-try is when you migrate one or more records from the **Show Errors** page, which is described later in this document. Retry = true is only used by the **Show Errors** page and should not be used from extensions.
### **Usage without staging tables:**
### Usage without staging tables:
The overall workflow is:
@ -75,58 +75,58 @@ The overall workflow is:
_Figure 1: sequence diagram of the data migration without staging tables _
The following example shows how to migrate items without staging tables:
```al
trigger OnRun();
var
ItemDataMigrationFacade: Codeunit "Item Data Migration Facade";
ItemNumber: Integer;
ItemJson: Text;
ItemDataMigrationFacade: Codeunit "Item Data Migration Facade";
ItemNumber: Integer;
ItemJson: Text;
begin
// loop on items retrieved through a web service for example
for ItemNumber := 1 to ExternalWebService.GetItemCount do begin
ExternalWebService.GetItem(ItemNumber,ItemJson);
// create item using the facade
if not ItemDataMigrationFacade.CreateItemIfNeeded(ItemJson.ItemNumber,ItemJson.ItemName1,
ItemJson.ItemName2,ConvertItemType(ItemJson.ItemType)) then
exit; // item already exists
// set some fields using the facade
ItemDataMigrationFacade.SetVendorItemNo(ItemJson.VendItemNumber);
ItemDataMigrationFacade.SetUnitVolume(ItemJson.Volume);
ItemDataMigrationFacade.SetAlternativeItemNo(ItemJson.AltItemNumber);
if ItemJson.PrimaryVendor <\> '' then
ItemDataMigrationFacade.SetVendorNo(ItemJson.PrimaryVendor);
// migrate dependencies
MigrateItemUnitOfMeasure(ItemDataMigrationFacade,ItemJson);
// modify the item (+run trigger) to save the changes made by setters
ItemDataMigrationFacade.ModifyItem(true);
// update the status in the migration dashboard
DataMigrationStatusFacade.IncrementMigratedRecordCount('My Migration Type',
Database::Item,1);
end;
// loop on items retrieved through a web service for example
for ItemNumber := 1 to ExternalWebService.GetItemCount do begin
ExternalWebService.GetItem(ItemNumber,ItemJson);
// create item using the facade
if not ItemDataMigrationFacade.CreateItemIfNeeded(ItemJson.ItemNumber,ItemJson.ItemName1,
ItemJson.ItemName2,ConvertItemType(ItemJson.ItemType)) then
exit; // item already exists
// set some fields using the facade
ItemDataMigrationFacade.SetVendorItemNo(ItemJson.VendItemNumber);
ItemDataMigrationFacade.SetUnitVolume(ItemJson.Volume);
ItemDataMigrationFacade.SetAlternativeItemNo(ItemJson.AltItemNumber);
if ItemJson.PrimaryVendor <\> '' then
ItemDataMigrationFacade.SetVendorNo(ItemJson.PrimaryVendor);
// migrate dependencies
MigrateItemUnitOfMeasure(ItemDataMigrationFacade,ItemJson);
// modify the item (+run trigger) to save the changes made by setters
ItemDataMigrationFacade.ModifyItem(true);
// update the status in the migration dashboard
DataMigrationStatusFacade.IncrementMigratedRecordCount('My Migration Type',
Database::Item,1);
end;
end;
procedure MigrateItemUnitOfMeasure(ItemDataMigrationFacade : Codeunit "Item Data Migration Facade";
ItemJson : Text);
var
MyUnitCodeStagingTable: Record "My Unit Code Staging Table";
DataMigrationStatusFacade: Codeunit "Data Migration Status Facade";
DescriptionToSet: Text\[10\];
UnitCodeJson: Text;
begin
if ItemJson.UnitCode = '' then
// log an error using the Data migration façade
DataMigrationStatusFacade.RegisterErrorNoStagingTablesCase(
'My Migration Type',Database::Item,'Unit of measure is empty.');
if ExternalWebService.GetUnitCode(ItemJson.UnitCode,UnitCodeJson) then
DescriptionToSet := UnitCodeJson.Description;
ItemDataMigrationFacade.CreateUnitOfMeasureIfNeeded(ItemJson.UnitCode, DescriptionToSet);
// set the unit of measure on the item
ItemDataMigrationFacade.SetBaseUnitOfMeasure(ItemJson.UnitCode);
end;
procedure MigrateItemUnitOfMeasure(ItemDataMigrationFacade : Codeunit "Item Data Migration Facade";
ItemJson : Text);
var
MyUnitCodeStagingTable: Record "My Unit Code Staging Table";
DataMigrationStatusFacade: Codeunit "Data Migration Status Facade";
DescriptionToSet: Text\[10\];
UnitCodeJson: Text;
begin
if ItemJson.UnitCode = '' then
// log an error using the Data migration façade
DataMigrationStatusFacade.RegisterErrorNoStagingTablesCase(
'My Migration Type',Database::Item,'Unit of measure is empty.');
if ExternalWebService.GetUnitCode(ItemJson.UnitCode,UnitCodeJson) then
DescriptionToSet := UnitCodeJson.Description;
ItemDataMigrationFacade.CreateUnitOfMeasureIfNeeded(ItemJson.UnitCode, DescriptionToSet);
// set the unit of measure on the item
ItemDataMigrationFacade.SetBaseUnitOfMeasure(ItemJson.UnitCode);
end;
```
_Figure 2: Example of Item and Item Unit of Measure migration without staging tables _
### **Usage with staging tables:**
### Usage with staging tables:
The overall workflow is:
@ -146,77 +146,78 @@ The overall workflow is:
[![ ][image2]][anchor2]
_Figure 3: Simplified sequence diagram of the data migration with staging tables _
_Figure 3: Simplified sequence diagram of the data migration with staging tables_
Below is a simplified example showing how to create an item:
\[EventSubscriber(ObjectType::Codeunit, Codeunit::"Item Data Migration Facade", 'OnMigrateItem', '', true, true)\]
```al
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Item Data Migration Facade", 'OnMigrateItem', '', true, true)]
procedure OnMigrateItem(VAR Sender : Codeunit "Item Data Migration Facade";RecordIdToMigrate : RecordId);
var
MyItemStagingTable : Record "My Item Staging Table";
MyItemStagingTable : Record "My Item Staging Table";
begin
// handle the event if it targets this extension's staging table
if RecordIdToMigrate.TableNo <\> Database::"My Item Staging Table" then
exit;
MyItemStagingTable.Get(RecordIdToMigrate);
// create item using the facade
if not Sender.CreateItemIfNeeded(MyItemStagingTable.ItemNumber,MyItemStagingTable.ItemName1,
MyItemStagingTable.ItemName2,ConvertItemType(MyItemStagingTable.ItemType)) then
exit; // item already exists
// set some fields using the facade
Sender.SetVendorItemNo(MyItemStagingTable.VendItemNumber);
Sender.SetUnitVolume(MyItemStagingTable.Volume);
Sender.SetAlternativeItemNo(MyItemStagingTable.AltItemNumber);
if MyItemStagingTable.PrimaryVendor <\> '' then
Sender.SetVendorNo(MyItemStagingTable.PrimaryVendor);
// modify the item (+run trigger) to save the changes made by setters
Sender.ModifyItem(true);
// handle the event if it targets this extension's staging table
if RecordIdToMigrate.TableNo <\> Database::"My Item Staging Table" then
exit;
MyItemStagingTable.Get(RecordIdToMigrate);
// create item using the facade
if not Sender.CreateItemIfNeeded(MyItemStagingTable.ItemNumber,MyItemStagingTable.ItemName1,
MyItemStagingTable.ItemName2,ConvertItemType(MyItemStagingTable.ItemType)) then
exit; // item already exists
// set some fields using the facade
Sender.SetVendorItemNo(MyItemStagingTable.VendItemNumber);
Sender.SetUnitVolume(MyItemStagingTable.Volume);
Sender.SetAlternativeItemNo(MyItemStagingTable.AltItemNumber);
if MyItemStagingTable.PrimaryVendor <\> '' then
Sender.SetVendorNo(MyItemStagingTable.PrimaryVendor);
// modify the item (+run trigger) to save the changes made by setters
Sender.ModifyItem(true);
end;
```
_Figure 4: Example of event subscriber for Item migration _
_Figure 4: Example of event subscriber for Item migration_
__
Below is another example showing how to use additional events to set fields that reference other tables, here the unit of measure:
\[EventSubscriber(ObjectType::Codeunit, Codeunit::"Item Data Migration Facade", 'OnMigrateItemUnitOfMeasure', '', true, true)\]
```al
[EventSubscriber(ObjectType::Codeunit, Codeunit::"Item Data Migration Facade", 'OnMigrateItemUnitOfMeasure', '', true, true)]
procedure OnMigrateItemUnitOfMeasure(VAR Sender : Codeunit "Item Data Migration Facade";RecordIdToMigrate : RecordId);
var
MyItemStagingTable : Record "My Item Staging Table";
MyUnitCodeStagingTable : Record "My Unit Code Staging Table";
DescriptionToSet: Text\[10\];
MyItemStagingTable : Record "My Item Staging Table";
MyUnitCodeStagingTable : Record "My Unit Code Staging Table";
DescriptionToSet: Text[10];
begin
// handle the event if it targets this extension's staging table
if RecordIdToMigrate.TableNo <\> Database::"My Item Staging Table" then
exit;
MyItemStagingTable.Get(RecordIdToMigrate);
if MyItemStagingTable.UnitCode = '' then
error('Unit of measure is empty.');
MyUnitCodeStagingTable.SetRange(UnitCode,MyItemStagingTable.UnitCode);
if MyUnitCodeStagingTable.FindFirst then
DescriptionToSet := MyUnitCodeStagingTable.Description;
// create the unit of measure through the facade
Sender.CreateUnitOfMeasureIfNeeded(MyItemStagingTable.UnitCode, DescriptionToSet);
// set the unit of measure on the item
Sender.SetBaseUnitOfMeasure(MyItemStagingTable.UnitCode);
// modify the item to save the changes made by setter
Sender.ModifyItem(false);
// handle the event if it targets this extension's staging table
if RecordIdToMigrate.TableNo <\> Database::"My Item Staging Table" then
exit;
MyItemStagingTable.Get(RecordIdToMigrate);
if MyItemStagingTable.UnitCode = '' then
error('Unit of measure is empty.');
MyUnitCodeStagingTable.SetRange(UnitCode,MyItemStagingTable.UnitCode);
if MyUnitCodeStagingTable.FindFirst then
DescriptionToSet := MyUnitCodeStagingTable.Description;
// create the unit of measure through the facade
Sender.CreateUnitOfMeasureIfNeeded(MyItemStagingTable.UnitCode, DescriptionToSet);
// set the unit of measure on the item
Sender.SetBaseUnitOfMeasure(MyItemStagingTable.UnitCode);
// modify the item to save the changes made by setter
Sender.ModifyItem(false);
end;
```
_Figure 5: Example of event subscriber for Item Unit of Measure migration _
_Figure 5: Example of event subscriber for Item Unit of Measure migration_
### **Combining both approaches:**
### Combining both approaches:
If you want to migrate additional entities, the **Data Migration **framework lets you initialize the migration with entities other than master data. In this case, the **Data Migration Overview** page will show additional lines. Item, vendor, customer, an general ledger accounts are migrated with an event driven approach, and the additional entities are migrated by calling an extension codeunit **OnRun** method.
## **Error handling with staging tables:**
## Error handling with staging tables:
The migration starts by calling **RUN** on the façade codeunit. Errors thrown during the call are captured by **GETLASTERRORTEXT** and displayed when you choose the **Show Errors** action on the **Data Migration Overview** page.
[![ ][image3]][anchor3]
_Figure 6: List of errors shown when clicking **Show Errors** on the **Data Migration Overview** page _
_Figure 6: List of errors shown when clicking **Show Errors** on the **Data Migration Overview** page_
__
@ -224,7 +225,7 @@ The **Edit Record** action opens a view of the staging table, where you can edit
[![ ][image4]][anchor4]
_Figure 7: Edit a staging table record _
_Figure 7: Edit a staging table record_
__
@ -238,14 +239,14 @@ When migrating data without staging tables, errors can be registered manually by
Errors will be displayed in the error list, but you cannot open and edit records because there is no staging table. The Edit action will not be available.
## **Limitations:**
## Limitations:
* Data migration will fail if there are customers, vendors, items in the database and if these entities are selected for migration. For example, if you choose to migrate items and your company already contains items, you will get an error. This should not be an issue if you migrate your data from another tool to NAV, in which case you will most likely start on a fresh empty company. However, if you just want to import additional items to a company with existing items, then it is not supported by the framework. however, you can still use the different functions provided by the different codeunits (such as **Item Data Migration Facade**) to create the entities without strong coupling on the NAV data model.
* **G/L entries** are deleted automatically.
* There is no automated rollback in case of failure: data that is successfully migrated will be commited, and data that is not successfully migrated with be shown in the errors list. The retry feature (in case of staging tables) then makes it possible for you to retry individual entities or ignore them.
## **Usages in NAV:**
## Usages in NAV:
The Data Migration Façade is available starting from version 2018\.
@ -264,7 +265,7 @@ The following Entity data migration façade codeunits are available:
* COD6113 (**Item Data Migration Facade**)
* COD6114 (**Ex. Rate Data Migration Facade**)
## **References:**
## References:
Façade pattern on Wikipedia: https://en.wikipedia.org/wiki/Facade\_pattern

View file

@ -4,7 +4,7 @@ weight = 1440
+++
# Variant Façade
By Nikola Kukrika, Waldo and Gary Winter
By Nikola Kukrika, waldo and Gary Winter
# Abstract

View file

@ -18,7 +18,7 @@ A design pattern is a repeatable template of how to solve a common development c
In 2013, Microsoft NAV Dev Team and prominent members of the community collaborated on a Community and Microsoft collection of commonly needed / used Design Patterns.
For some history on this, Waldo's posts [Code is Poetry](https://www.waldo.be/2013/06/14/code-is-poetry/) and [Design is Philosophy](https://www.waldo.be/2013/08/28/design-is-philosophy-2/) are a great read.
For some history on this, waldo's posts [Code is Poetry](https://www.waldo.be/2013/06/14/code-is-poetry/) and [Design is Philosophy](https://www.waldo.be/2013/08/28/design-is-philosophy-2/) are a great read.
### Contributing