Create or extend Data Migration Framework

Create the Staging Table

1) In the AOT create a staging table for the entity, with the following table properties:

Property Value
SaveDataPerCompany NO
SupportInheritance NO
TableType Regular
ConfigurationKey DMF
ValidTimeStateFieldType None

2) Create fields with the following properties

Field name EDT Enum
DefinitionGroup DMFDefinitionGroupName
IsSelected DMFIsSelected NoYes
TransferStatus DMFTransferStatus
ExecutionId DMFExecutionId

DMFBEMA

IMPORTANT!
The wizard creates these four fields, but the fields “IsSelected” and “TransferStatus“ are created without EDT/Enum. They must be inserted manually!

3) Create field groups with the following properties:

Field group name Label Field
ExecutionList Execution list DefinitionGroup, IsSelected, TransferStatus, ExecutionId
Enabled Enabled OPTIONAL. Fields from the staging table which you want to make part of   template by default
<<FunctionName_Sequence>>Ex.: GeneratePostalAddress_2 <<Description offunction>> Fields from staging table which are the source for the   specified method (fields that are used in the method)
NB.: The field group “Generate” must be created even if there are no fields in them. If in the class is present the method “Generate”, the DMF table must have the associated field group (empty or not). For example, the table “DMFProductEntity” has a field group named GenerateItemGroupCompany_2”,  that is empty, but the class “DMFProductEntityClass” has a method called “GenerateItemGroupCompany”.

4)      Create a primary index for the table:

Index name Properties Field(s)
<<any name>>Ex.: Idx
AllowDuplicates NO
AlternateKey YES
DefinitionGroup, ExecutionId, fields from staging table   to define uniqueness

5)      Specify the relationship between the staging table and the target table

Create an Enum fields in the target entity

The Enum field in the target entity is represented by a string field in the staging table. You must create a new extended data type (EDT) of type string, and of appropriate length to take the enum label strings.

Capture

HcmEmployment.EmploymentType = Enum HcmEmploymentType
DMFEmployeeEntity. EmploymentType = Type String -> EDT = DMFEmploymentType

Capture

RefRecId

If the target table contains fields that are RecIds from other tables, you must convert the natural key to a RecId. There are two options:

  • Add a data source so that the referenced table can be added to the target entity query

  • Create a function

Add a datasource

For example, the target VendTable contains VendExceptionGroup, which is a RecId that comes from VendExceptionGroup Table.

Capture

In this case, the staging table must include
VendExceptionGroup. We add the table in the query, under VendTable datasource. The relationship must be specified manually.

Capture

The staging field DMFVendorEntity.VendExecptionGroup must be mapped to the target field query for DMFVendorTargetEntity . DS:VendExceptionGroup.VendExceptionGroup

Create a function

Create a method on the entity class to convert the string to a RecId. For example, the method DMFCustomerEntity.GenerateCompanyIdNAF:

public container GenerateCompanyIdNAF(boolean _stagingToTarget = true)
{
    CompanyNAFCode  companyNAFCode;
    container       res;

    if (_stagingToTarget)
    {
        select firstOnly1 RecId from companyNAFCode where companyNAFCode.CompanyIdNAF == entity.CompanyIdNAF;
        res = [companyNAFCode.RecId];
    }
    else
    {
        if (target.CompanyNAFCode)
        {
            select firstOnly1 CompanyIdNAF, RecId from companyNAFCode where companyNAFCode.RecId == target.CompanyNAFCode;
        }

        res = [companyNAFCode.CompanyIdNAF];
    }

    return res;
}

When you create a function approach, you must create a field group for the staging table, and the return fields on the target must be specified in the getReturnFields method in the entity class.

case methodStr(DMFCustomerEntityClass, GenerateCompanyIdNAF) :
     con += [fieldstrToTargetXML(fieldStr(CustTable, CompanyNAFCode))]; 
     break;

Create a class

1)      In the AOT create a class for the entity with the following properties:

  • [DMFAttribute(true)]

  • DMFClassName extends DMFEntityBase

  • Declare the object for the staging table with the name “entity”.

  • Declare the object for the main table for the target entity with the name “target”.

  • The following example shows how the DMFBEMATableEntityClass is declared.

[DMFAttribute(true)]
public class DMFBEMATableEntityClass extends DMFEntityBase
{
    DMFBEMATableEntity entity;
    BEMATable target;
}

2)      Create the new method:

  • Must take the staging table as parameters

  • The value entity should be initialized from a parameter. The following example shows the DMFBEMATableEntity new method

public void new(DMFBEMATableEntity _entity)
{
    entity = _entity;
}

3)      Create the construct method:

  • Must take the staging table as a parameter

  • Must create and return the object of the current class by using a parameter. The following example shows the DMFBEMATableEntity construct method

public static DMFBEMATableEntityClass construct(DMFBEMATableEntity _entity)
{
    DMFBEMATableEntityClass entityClass = new DMFBEMATableEntityClass(_entity);
    return entityClass;
}

4)      Create the setTargetBuffer method:

  • A target entity can have multiple data sources. One of the data sources is the main table that represents the entity. In the setTargetBuffer method, the parameter _dataSourceName represents the data sources that are present in the target entity query

  • Depending on the data source, you may need to initialize a local instance of the table for the data source. The local instance can then be used in the functions that are required for data migration. The target should be initialized by the main table that represents the target entity

public void setTargetBuffer(Common _common, Name _dataSourceName = "")
{
    switch(_common.TableId)
    {
        case tableNum(BEMATable) :   
            target = _common;
            break;
    }
}

5)      Set the RunOn property of the class to the value CalledFrom

Write functions to import and export data

You can map data from the staging to the target table in two ways:

  1. Assign fields: A field from staging is directly assigned to a field in target. In this case the data types for the staging and target fields must be same

  2. Write a function to transform the field values from staging to target. You can write X++ functions to transform and map data from staging to target

Write functions

Data import and export functions that you define must perform the following actions:

  1. Input (Source): the entire staging record is available as a local variable to the class, so there is no need to pass any parameters to this class

  2. Output (Target):  as a result of executing the function, zero or multiple fields in the target entity are set. The return type of the function is a container, which can hold zero or more values to set on the target

The sequence of values that are returned by a particular function must be defined in the getReturnFields method.

addStagingLink method

The addStagingLink method is used to define the relationship between the staging and target table when it cannot be defined by using the relations property of the staging table.

The target query and the staging record are available in the method, so the range between target and staging can be added using code.

Example: DMFEmployeeEntityClass

public static Query addStagingLink(Query query, TableId _entityTableId, Common _staging)
{
    QueryBuildDataSource    qbd;

    qbd = query.dataSourceTable(tableNum(HcmWorker));
    qbd.addRange(fieldNum(HcmWorker,PersonnelNumber)).value(_staging.(fieldNum(DMFEmployeeEntity,PersonnelNumber)));
    return query;
}

getReturnFields method

The getReturnFields method is used to specify the default output or target fields for the functions that are used for data migration.

Parameters include:

  •  _entity: entity name

  • _name: function name

The function must return a container, as well as the name of the data source in the target entity query with which the method should be executed, and the name of the data source field in the target entity query which should be initialized by executing the function.

public static container getReturnFields(Name _entity, MethodName _name)
{
    DataSourceName dataSourceName = queryDataSourceStr(DMFEmployeeTargetEntity, HcmEmploymentDetail);
    Container      con            = [dataSourceName];

    Name fieldstrToTargetXML(FieldName _fieldName)
    {
        return DMFTargetXML::findEntityTargetField(_entity,dataSourceName,_fieldName).XMLField;
    }

    switch (_name)
    {

        case methodStr(DMFEmployeeEntityClass,GenerateReasonCode) :
            con += [fieldstrToTargetXML(fieldStr(HcmEmploymentDetail, TransitionReasonCode))];
            break;

        //change datasource
        case methodStr(DMFEmployeeEntityClass,GenerateDefaultDimension) :
            dataSourceName = queryDataSourceStr(DMFEmployeeTargetEntity, HcmEmployment);
            con            = [dataSourceName];
            con += [fieldstrToTargetXML(fieldStr(HcmEmployment, DefaultDimension))];
            break;
        default :
            con = conNull();
    }

    return con;
}

This tells the migration tool which table and field, the value passed back from the generate method should go.

Capture

Error: Failed to logon to Microsoft Dynamics AX

Object Server 01: An error has occurred in the services framework. Method: AifMessageInspector::AfterReceiveRequest. Error: System.ServiceModel.FaultException: Failed to logon to Microsoft Dynamics AX.
at Microsoft.Dynamics.Ax.Services.AxServiceOperationContext.InitializeSession()
at Microsoft.Dynamics.Ax.Services.AxServiceOperationContext.InitializeContext()
at Microsoft.Dynamics.Ax.Services.AxServiceOperationContext.Attach(OperationContext owner)
at System.ServiceModel.ExtensionCollection`1.InsertItem(Int32 index, IExtension`1 item)
at System.Collections.Generic.SynchronizedCollection`1.Add(T item)
at Microsoft.Dynamics.Ax.Services.AifMessageInspector.AfterReceiveRequest(Message& request, IClientChannel channel, InstanceContext instanceContext)

Workaround:
Add the AOS in the users’ list with System User and System Administrator permissions.

Enterprise Portal: configuring lookup

You can override the dataSetLookup method of the field in the data source, to control the lookup behavior. Here is an example:

void dataSetLookup(SysDataSetLookup sysDataSetLookup)
{
    List                                list = new List(Types::String);
    LogisticsLocationSelectionLookup    logisticsLocationSelectionLookup;
    CustTable                           custTable;
    Common                              partyRecord;
    DirPartyLocation                    partyLocation;
    container                            locations;
    Query                               query = new Query();
    QueryBuildDataSource                qbdAddress;
   
    partyRecord = DirPartyPostalAddressFormHandler::getTransactionEntity(element.args().record());
    custTable = CustTable::find(salesTable.CustAccount);
 
    logisticslocationselectionlookup = logisticslocationselectionlookup::construct(partyrecord);
    logisticslocationselectionlookup.parmAllowAddAddress(false);
    logisticslocationselectionlookup.parmAllowEnhancedSelect(false);
    logisticslocationselectionlookup.parmUseLookupValue(true);
 
    logisticslocationselectionlookup.addRoleByType(LogisticsLocationRoleType::Delivery);
    logisticslocationselectionlookup.parmreturnlocation(true);
 
    while select Location from partyLocation
        where partyLocation.Party == custTable.Party
    {       
locations += partyLocation.Location;     }       qbdAddress = query.addDataSource(tablenum(LogisticsPostalAddress));     qbdAddress.fields().clearFieldList();     qbdAddress.addRange(fieldnum(LogisticsPostalAddress, Location)).value(con2str(locations));       //methods and fields of the LogisticsPostalAddress table     list.addEnd(tablemethodstr(LogisticsPostalAddress, getLocationName) + '**');//display method     list.addEnd(tablemethodstr(LogisticsPostalAddress, getDescription) + '**');//display method     list.addEnd(fieldStr(LogisticsPostalAddress, Street)); //field     list.addEnd(tablemethodstr(LogisticsPostalAddress, getDealerCode) + '**');//display method     list.addEnd(tablemethodstr(LogisticsPostalAddress, getExternalAddressCode) + '**');//display method       sysDataSetLookup.parmSelectField(tablemethodstr(LogisticsPostalAddress, getLocationNumber) + '**');       sysDataSetLookup.parmLookupFields(list);     sysDataSetLookup.parmQuery(query); }

The result is:

Capture

DMF – Create custom entity mapping by using the wizard

When you create a custom entity, you must create a staging table, a project, a query, a class, and functions. You must always create the class and functions manually, but you can use one of two methods to create the staging table, project, and query:

  • Use the Create a custom entity for data import/export wizard.
  • Create the staging table and query manually

Name

Definition

Query

the query is a definition of a target table. You can add   relationships if required for mapping

Table

The table suffixed with “Entity” is the staging table to use for mapping

EDT

the foreign key using RecId. The EDT generated, is a   string field representing the code

Class

The class is where we can put business logic. For example, the EDT with   RecId need to be resolved

Creation by using the “Create a custom entity for migration

  • Data Import/Export Framework > Common > Create a custom entity for data import/export [Data migration framework > Common > Create a custom entity for migration] (2012)

Image

  • Select a table that is related to the entity that you want to migrate, and then click Next
  • On the “Select code generation parameters”, the wizard will suggest names for the
    staging table, query, and class

Image

  • On the “Wizard complete” page, click “Finish”. The Data Import/Export Framework
    opens the AOT and creates a project for the custom entity
  • If the table that you selected uses an extended data type (EDT), then you will be asked whether you want to add the ForeignKey relation from the EDT to the new staging table. Click Yes to create the ForeignKey relationship

 

Data import/export framework in AX 2012 R2

Introduction

Data Migration Process

1) Prepare Source Data
The file must have a header, which includes the field names identifying each data column.

2) Define type of AX entity to be used
Define entities that will be loaded to AX. For example, Customers, Vendors…

3) Mapping and validation of source file fields to staging AX fields
The DMF tool will allow you to map each field from the source file to an AX field in the staging table. The DMF uses the field names to map the fields in the staging table.

4) Transferring data from source file to the staging table
Once fields from the source file have been mapped to AX fields in the staging table data should be ready to be transferred to the staging table.

5) Transfer of data from staging to target AX destination
Once data has been reviewed and validated in the stating table, the data is then moved to the target AXentity and AX records are created.

Configuration

Data Migration Configuration

1) Determine entities – DMF > Setup > Target entities
There is a set of predefined entities. These entities also include procedures to move data from the staging table to the destination.

2) Define the source data format – DMF > Setup > Source data formats

  • Create a new data format
  • File format: delimited by a character or fixed
  • First row header: indicate if first row of the file, is a header row
  • Row / column delimiter: which character will be used to determine the end of the line / column
  • Text qualifier: indicates characters to encase text and commas
  • Regional settings: regional settings for the file
  • Role separator: used to indicate multiple entries, for example: 317-509 ; 508-999

3) Define the data format for each entity in the source environment
The data format can be a file that is delimited or fixed width, an ODBC data source, or a Microsoft Dynamics AX table

4/5) Define the processing group / Source to staging table

  • Create a new processing group
  • Entities:
    1. Create a new entity and select the source data format
    2. Run business logic: two flags to enable the check for AX business logic
    3. Sample file path: the path of the file
    4. Generate source mapping: maps the source file against the staging table. If you use AX column names as your header, the DMF tool will automatically try to do the mapping
    5. Modify source mapping: the user can review and modify the mappings
    6. Validate: the validation ensure that all fields in the source file are correctly mapped to the staging table
    7. View target mapping: this option can be used to validate the mappings
    8. Preview source file: the user can view a preview of the source file before the file is copied to the staging table
  • Get staging data: data is ready to be copied to the staging table. This button is used to start the process. A message indicate that the data has been copied to the staging table

6) Moving data from staging table to final ax destination
Select the “Copy data to target”.
Processing Group

Manage printer settings for SSRS Report in AX 2012

Goal: how to manage the printer settings for reports in AX 2012.

The following example is based on the new report Quality Orders in the Invent Module.

STEP 1

Create the SSRS Report

  • Report name:          InventQualityOrder

  • Contract name:       InventQualityOrderTableContract

  • Controller name:    InventQualityOrderTableController

STEP 2

Add new values to the BaseEnums:

  • PrintMgmtNodeType; add “QualityOrders”

  • PrintMgmtDocumentType; add “QualityOrders”

STEP 3

Create an extension of the PrintMgmtNode class:

 

public class PrintMgmtNode_QualityOrder extends PrintMgmtNode

public List getDocumentTypes() { List docTypes = new List(Types::Enum);   docTypes.addEnd(PrintMgmtDocumentType::QualityOrders);   return docTypes; }   public RefTableId getReferencedTableId() { return tablenum(InventQualityOrderTable); } 

STEP 4

Modify the following classes:

PrintMgmtNode

1) Add the following code to the method “construct”:

case PrintMgmtNodeType::QualityOrdersWCH:
return new PrintMgmtNode_QualityOrderWCH();

A PrintMgmtNode class defines a level at which Print Management information can be associated. 

PrintMgmtDocType

1) Method getDefaultReportFormat()

case PrintMgmtDocumentType::QualityOrders:
return ssrsReportStr(InventQualityOrder, Report);

2) Method getQueryRangeFields()

case PrintMgmtDocumentType::QualityOrders:
fields.addEnd(fieldNum(InventQualityOrderTable, QualityOrderId));
break;

3) Method getQueryTableId()

case PrintMgmtDocumentType::QualityOrders:
tableId = tableNum(InventQualityOrderTable);
break;

The PrintMgmtDocType class represents the document types that are available in print management.

PrintMgmtHierarchy_Invent

1) Method getNodesImplementation()

supportedNodes.addEnd(PrintMgmtNodeType::QualityOrders);

2) Method getParentImplementation()

case PrintMgmtNodeType::QualityOrders:
result.parmReferencedTableBuffer(null);
result.parmNodeDefinition(PrintMgmtNode::construct(PrintMgmtNodeType::Invent));
break;

PrintMgmtNode_Invent

1) Method getDocumentTypes()

docTypes.addEnd(PrintMgmtDocumentType::QualityOrders);

The PrintMgmtNode_Invent class is responsible for managing print management settings at the module level in the inventory module. Add the new document type in the method getDocumentTypes().

STEP 5

Create an extension of the FormLetterReport class:

class InventFormLetterReport_QualityOrder extends FormLetterReport
{
    InventTransType transType;
}
 
protected container getDefaultPrintJobSettings(PrintSetupOriginalCopy _printCopyOriginal)
{
    return new PrintJobSettings().packPrintJobSettings();
}
 
protected PrintMgmtDocumentType getPrintMgmtDocumentType()
{
    return PrintMgmtDocumentType::QualityOrders;
}
 
protected PrintMgmtHierarchyType getPrintMgmtHierarchyType()
{
    return PrintMgmtHierarchyType::Invent;
}
 
protected PrintMgmtNodeType getPrintMgmtNodeType()
{
    return PrintMgmtNodeType::QualityOrders;
}

STEP 6

Modify the FormLetterReport class:  

1) Method construct()  

case PrintMgmtDocumentType::QualityOrders:
return new InventFormLetterReport_QualityOrder();

The FormLetterReport class controls the printing of documents.

STEP 7

Create the controller, which extends the TradeDocumentReportController class, which allows to manage multiple prints.

 

The following dialog aims to provide the possibility to choose printer settings that are different from the ones defined in the standard configuration (Inventory and warehouse management > Setup > Inventory and warehouse management parameters > Print management):

 

The dialog offers two scenarios:

- “Use print management destination” is flagged – in this case the system takes the settings from Inventory and warehouse management parameters

- Otherwise, the system takes the printer settings from the dialog (“Current print destination”).

 

In any case, it is necessary to overwrite the following methods in the controller:

 

initFormLetterReport()

protected void initFormLetterReport()
{
    printCopyOriginal = this.parmArgs().parmEnum();
 
    this.initializeJournalLists(this.parmArgs());
 
    formLetterReport = FormLetterReport::construct(PrintMgmtDocumentType::QualityOrders);
 
    formLetterReport.parmPrintType(printCopyOriginal);
 
    if (printCopyOriginal == PrintCopyOriginal::OriginalPrint)
    {
        // Always use the print mgmt destinations when reprinting for the OriginalPrint case.
        formLetterReport.parmUsePrintMgmtDestinations(true);
    }  
 
    super();
}
 

output()

This method is called from the runPrintManagement method, for every selected journal.

protected void output()
{
    InventQualityOrderTableContractWCH rdpContract = this.parmReportContract().parmRdpContract() as InventQualityOrderTableContractWCH;
 
    //If the flag is not set, I set the current print destination in case of original/copy printing
    if (!rdpContract.parmUsePrintManagementSettings())
    {
        formLetterReport.parmDefaultCopyPrintJobSettings(this.parmReportContract().parmPrintSettings()); formLetterReport.parmDefaultOriginalPrintJobSettings(this.parmReportContract().parmPrintSettings());
       formLetterReport.parmUsePrintMgmtDestinations(false);
    }
 
   // load the new setup for printing the report
   formLetterReport.loadPrintSettings(inventQualityOrderTable, inventQualityOrderTable, CompanyInfo::languageId());
   this.parmReportContract().parmRdlContract().parmLanguageId(CompanyInfo::languageId());
 
   super();
}

 

STEP 8

Go to method populate() in table PrintMgmtReportFormat and add this line of code:

addAx(PrintMgmtDocumentType::QualityOrders);

Finally go to:
Inventory and warehouse management > Setup > Inventory and warehouse management parameters > Print management
Here add a new node for the new report and set the print management settings.

For further information see also:

- SalesQuotationController class – for example

- http://msdn.microsoft.com/en-us/library/tradedocumentreportcontroller.aspx

- http://dynamicsaxgyan.wordpress.com/2011/08/19/learn-quick-first-ssrs-report-in-dynamics-ax-2012/ – create a new SSRS report