How to Use the Table Map Extension in Dynamics 365 Finance and Operations

By Dalton Roll | July 11, 2022

Modifying existing table map objects in AX 2012 is now a thing of the past when it comes to development in D365 F&O. They cannot be extended due to being refactored into a class model. To know more about why table map objects could not be upgraded to the new version, see Table map extension.

Recently, Microsoft introduced an interface hierarchy with the refactoring of table map objects. In a Microsoft article, the author explains that the hierarchy has an abstract base class to hold abstract methods for fields and methods. These must exist for each table implementing the map interface. Also, the base class can have global methods that are common across all tables. A derived class is created for each table to implement the base class.

To help decipher the high-level overview and article, I have put together the example below.

Development Purpose Example

For this post, we will extend the new SalesPurchLine map interface (SalesPurchLineInterface class) to:

  • Add a new mapped field
  • Add a new table-specific method
  • Add a new global method

Follow these steps to test out this feature:

1 - Add a new field to SalesLine and PurchLine table extensions:

Table Map Extension in Dynamics 365 Finance and Operations SalesLine

SalesLine

Table Map Extension in Dynamics 365 Finance and Operations PurchLine

PurchLine

2 - Create a new interface class: 

  • It is recommended to have the base class of additional class hierarchies is concrete and not abstract
  • Note: while it is recommended to create a new class hierarchy, it is possible to extend an existing class hierarchy

You can use the following example:

class SSiSalesPurchLineInterface
{
private SalesPurchLine salesPurchLine;

private void initSalesPurchLine(SalesPurchLine _salesPurchLine)
{
salesPurchLine = _salesPurchLine;
}

protected SalesPurchLine parmSalesPurchLine()
{
return salesPurchLine;
}

public static APiSalesPurchLineInterface createInstance(SalesPurchLine _salesPurchLine)
{
SalesPurchLineInterfaceFactoryAttribute attr = new SalesPurchLineInterfaceFactoryAttribute(tableId2Name(_salesPurchLine.tableId));

APiSalesPurchLineInterface instance = SysExtensionAppClassFactory::getClassFromSysAttribute(classStr(APiSalesPurchLineInterface), attr) as APiSalesPurchLineInterface;

if (!instance)
{
// If no derived class is found, return the base
// The base class will allow calls to this class
// Reason behind why I have table-specific methods throw errors
instance = new APiSalesPurchLineInterface();
}

instance.initSalesPurchLine(_salesPurchLine);

return instance;
}

// Parm method for new table map field
public Name parmSSiName()
{
throw error(Error::missingOverride(funcName()));
}

// Table specific method
public Name getTableName()
{
throw error(Error::missingOverride(funcName()));
}

// Global method used for each
public Name globalName()
{
return "Stoneridge Software";
}

}

3 - Create a derived class for the SalesLine table: 

[SalesPurchLineInterfaceFactory(tableStr(SalesLine))]
class SSiSalesLineSalesPurchLine extends SSiSalesPurchLineInterface
{
// Parm method to return map as SalesLine table
private SalesLine parmSalesLine()
{
return this.parmSalesPurchLine();
}

// Parm method for new map field
public Name parmSSiName()
{
return this.parmSalesLine().SSiName;
}

// Table specific method
public Name getTableName()
{
return "SalesLine";
}

}

4 - Create a derived class for PurchLine table: 

[SalesPurchLineInterfaceFactory(tableStr(PurchLine))]
class SSiPurchLineSalesPurchLine extends SSiSalesPurchLineInterface
{
// Parm method to return map as PurchLine table
private PurchLine parmPurchLine()
{
return this.parmSalesPurchLine();
}

// Parm method for new map field
public Name parmSSiName()
{
return this.parmPurchLine().SSiName;
}

// Table specific method
public Name getTableName()
{
return "PurchLine";
}

}

5 - Create an extension of SalesPurchLineInterface class to add access to a new instance: 

[ExtensionOf(classStr(SalesPurchLineInterface))]
final class SSiSalesPurchLineInterfaceClass_Extension
{
public SSiSalesPurchLineInterface ssiSalesPurchLineInterface()
{
return SSiSalesPurchLineInterface::createInstance(this.parmSalesPurchLine());
}

}

6 - Access a new instance from the original map instance: 

class TestMapExtension
{
/// <summary>
/// Runs the class with the specified arguments.
/// </summary>
/// <param name = "_args">The specified arguments.</param>
public static void main(Args _args)
{
///
/// SalesLine
///

SalesLine salesLine;
select firstonly salesLine;

salesLine.SSiName = "SSi SalesLine";

// Original mapping instance
SalesPurchLineInterface origInstance = salesLine.salesPurchLineInterface();
info(strFmt("%1", origInstance.parmOrderId()));

// Map extension instance
SSiSalesPurchLineInterface newInstance = salesLine.salesPurchLineInterface().ssiSalesPurchLineInterface();
info(strFmt("%1", newInstance.parmSSiName()));
info(strFmt("%1", newInstance.getTableName()));
info(strFmt("%1", newInstance.globalName()));

///
/// PurchLine
///

PurchLine purchLine;
select firstonly purchLine;

purchLine.SSiName = "SSi PurchLine";

// Original mapping instance
origInstance = purchLine.salesPurchLineInterface();
info(strFmt("%1", origInstance.parmOrderId()));

// Map extension instance
newInstance = purchLine.salesPurchLineInterface().ssiSalesPurchLineInterface();
info(strFmt("%1", newInstance.parmSSiName()));
info(strFmt("%1", newInstance.getTableName()));
info(strFmt("%1", newInstance.globalName()));
}


Under the terms of this license, you are authorized to share and redistribute the content across various mediums, subject to adherence to the specified conditions: you must provide proper attribution to Stoneridge as the original creator in a manner that does not imply their endorsement of your use, the material is to be utilized solely for non-commercial purposes, and alterations, modifications, or derivative works based on the original material are strictly prohibited.

Responsibility rests with the licensee to ensure that their use of the material does not violate any other rights.

Start the Conversation

It’s our mission to help clients win. We’d love to talk to you about the right business solutions to help you achieve your goals.

Subscribe To Our Blog

Sign up to get periodic updates on the latest posts.

Thank you for subscribing!