First 2020 post! Happy new year! Yes, I know it’s already past mid January…

When you add a field to a SysOperation Framework Data Contract the lookup that the framework creates (if the EDT has a lookup) is a simple, single select lookup. Let’s see how to create a multi select lookup in MSDyn365FO.

The SysOperation Framework and MVC

But first of all a bit of an introduction! The SysOperation Framework was introduced in Dynamics AX 2012 to replace the RunBase Framework, and is used to create processes that will be run by the batch server. The RunBase classes are still around in Dynamics 365 for Finance and Operations. Some standard processes still use it while others use it to later call a SysOperations Framework class.

While you still can use the RunBase Framework I’d just go with the SysOperations Framework as it performs better and uses a more modern software MVC design pattern (even though the idea of the MVC pattern exists since the 70s…).

To do this we need to create a Data Contract class for our data model, a Controller class that will define how the process is run, a Service class with the logic (which will be a DataProvider class for reports) and a UIBuilder class to change the behavior of the dialog. In the example I will list the legal entities and use the SysOperation classes to run a report.

Data Contract

The Data Contract class is our data model and needs to be tagged with the DataContract attribute. To create a multi selection lookup we’ll declare a List instead of a variable of the EDT that has the relation to the table that we need to multi select. The parm method needs to be tagged with the DataMemeber and AifCollectionType attributes.

Our Data Contract will look like this:

class AASSysOpsReportDC
    List selectedEntities;

        AifCollectionType('return', Types::String)
    public List parmSelectedLegalEntities(List _selectedEntities = selectedEntities)
        selectedEntities = _selectedEntities;

        return selectedEntities;


The SysOperation Framework will automatically create the UI of all the members tagged with the DataMember attribute, that’s another enhancement from the RunBase.

Service class (Data Provider)

The service class holds the logic. As I said this class will be different depending if we’re using it to run a report or to run a process in batch.

Data Provider

For a report we create a Data Provider (DP) class where we fill the temporary table that feeds data to the report:

class AASSysOpsReportDP extends SrsReportDataProviderPreProcess
    AASSysOpsReportTableTmp reportTmp;

    [SRSReportDataSet(tablestr(AASSysOpsReportTableTmp ))]
    public AASSysOpsReportTableTmp getTmpTable()
        select reportTmp;

        return reportTmp;


For a class running business logic we do the following, extending from a different class:

class AASSysOpsBatchService extends SysOperationServiceBase
    AASSysOpsBatchDC         contract;
    public void executeOperation(AASSysOpsBatchDC _contract)
        // do things


The controller is where we define how the class will run synchronously or asynchronously, it’s pretty straightforward:

class AASSysOpsReportController extends SrsReportRunController
    public void execute(Args _args)
        this.parmReportName(ssrsReportStr(AASTestReport, Report));

    static void main(Args _args)
        new AASSysOpsReportController().execute(_args);


Because I’m running a report I’m not defining how the class will run. If we were creating a Batch class the controller would look like this (note that, as the service, it’s extending a different class too):

class AASBatchClassController extends SysOperationServiceController
    public static void main(Args _args)
        AASBatchClassController controller      = AASBatchClassController::construct();


    public static AASBatchClassController construct(SysOperationExecutionMode _executionMode = SysOperationExecutionMode::Asynchronous)
        return new AASBatchClassController(identifierStr(AASSysOpsBatchService), methodStr(AASSysOpsBatchService, executeOperation), _executionMode);


It’s here where we tell the class how to run, in this example synchronously with the _executionMode parameter. We can use 4 different modes:

  • Synchronous: runs the process and leaves the UI locked until it finishes.
  • Asynchronous: runs the process in batch
  • Reliable asynchronous: runs the process in batch after clicking the OK button. While running it will be on the batch list, when it’s done it will disappear.
  • Scheduled batch: same as Reliable asynchronous but the job stays on the batch list when it’s done.

UIBuilder class

The UIBuilder class allows us to override how the DC class displays some elements or enhance the functionality of a field which is what we want to do:

class AASSysOpsReportUIBuilder extends SrsReportDataContractUIBuilder
    AASSysOpsReportDC   contract;
    DialogField		availableCompanies;

    public void postBuild()
        contract		= this.dataContractObject() as AASSysOpsReportDC;
        availableCompanies	= this.bindInfo().getDialogField(contract, methodStr(AASSysOpsReportDC, parmSelectedLegalEntities));


    public void postRun()
        Query		        query		    = new Query();
        QueryBuildDataSource    qbdsLegalEntity     = query.addDataSource(tablenum(OMLegalEntity));

        qbdsLegalEntity.fields().addField(fieldNum(OMLegalEntity, LegalEntityId));
        qbdsLegalEntity.fields().addField(fieldNum(OMLegalEntity, Name));

        container selectedFields = [tableNum(OMLegalEntity), fieldNum(OMLegalEntity, LegalEntityId)];

        SysLookupMultiSelectCtrl::constructWithQuery(this.dialog().dialogForm().formRun(), availableCompanies.control(), query, false, selectedFields);

We create a dialog field and initialize it with the parm method for our List in the DC. Then in the postRun method we call the SysLookupMultiSelectCtrl::constructWithQuery method. We can either pass an existing query or create one in X++ as I’ve done. Please note that there’s no call to super in these methods, otherwise the controls will be created by the SysOperation Framework and you’ll get a runtime error.

The result

With all these classes created we can now run the Controller class and try how the lookup looks like.

The records can be selected, as many as we want!

And now we can retrieve the data using the parm method in our Service or Data Provider class and use the values. Great!


Receive an email when a new post is published

Microsoft Dynamics 365 Finance & Operations technical architect and developer. Business Applications MVP since 2020.

Write A Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.