Create A D365 Batch Job

Share this:

Do you have a process that you need to run in the background of Microsoft Dynamics 365 F&SC? There are two ways to create a D365 batch job. The newer way is to use SysOperation Framework in D365. However, the older way is still very much supported and used within the system. Therefore, it is well worth understanding how to use and create a D365 batch job using RunBaseBatch.

Why Use A D365 Batch Job?

D365 Batch jobs allow you to run a process in the background. Most of often they are used to run processes that can take a long time to run. Or need to run on a scheduled re-occurrence. For example, a batch job can be run once a day at night, so as to not take processing power away from the users during the day.

That said, there are additional ways batch jobs can be very useful that users do not always remember.

Batch jobs allow users to offload work to the server, but then continue to use the system immediately, if the user does not need to use the output.

While some tasks require a process to finish before continuing on to the next steps, other processes do not. Users just care that they are run, but do not need to wait. Or perhaps they are the last step of a larger task.

Even if the process a user is running will only take a minute to run, why wait for the process to finish if you do not have to? The system can be directed to run the process in the background, while the user continues to work. This creates a more enjoyable user experience.

Additionally, when a process runs as a batch job, the code will continue to run, even if the user closes their browser.

How To Start A Batch Job

Before I explain how to create a D365 batch job using x++ code, I want to provide a high level overview of how to start a Batch Job.

To start, when a user click on a Menu Item in D365 that points to a Batch job class, the system will open a dialog form. This form will have a fast tab labeled ‘Run in the background‘ that provides a consistent list of options for the user to specify. These options are shown without a user needing to add code for them, simply by extending the RunBaseBatch class. More on that later.

The user can set the ‘Run in batch‘ radio button. This indicates whether the process should run on the server in the background. Otherwise, if unchecked, the process will run in the user’s browser, and they will need to wait on any output before being given control to continue using the system.

Additionally, users can specify several options that control the recurrence of the batch job. For example, users can specify the batch job should be run every 2 hours, for 20 times, then stop. Or, as another example, users could setup the batch job to run after day at 1am, until March the 3rd then stop. Or run on a reoccurrence with no end date.

Batch Job Management

After the batch job is created, users can view the status of the batch job by going to System administration>Inquiries>Batch jobs.

There are many tasks that can be performed on this form.

First, users can view existing batch jobs. See the status as to whether they are currently running, when they will next run, and when they last ran.

Secondly, users can view the recurrence of any given batch job. And what parameters were passed to the batch job.

Thirdly, each instance that the batch job was run in the past can be viewed. And the associated logs with each run.

Fourthly, alerts can be setup to inform users if a batch job experiences an error.

Fifthly, batch jobs can actually be created from this form. However, in my opinion it is more common to create batch jobs using their menu items. That said, functional users can create custom complex batch jobs that include multiple tasks or sub batch jobs by using this form.

To learn more on how to create a batch job using the Batch jobs form itself, see this Microsoft documentation here:

Lastly, the status of a batch job can be changed from this form. This is useful for the scenario where you normally have a job that runs on a recurrence, but you need to pause the process for a time, or cancel it altogether. This can be done by going to Batch job>Functions>Change status.

To find out more about batch processing, see the Microsoft documentation here.

Create A D365 Batch Job

As a developer, you regularly will be asked to create a D365 batch job. Again, you may decide to use SysOperation Framework in D365. But your other option is to create an x++ class that extends RunBaseBatch. This is the other way of creating a batch job.

On the positive side, the base Microsoft class RunBaseBatch already contains the core logic needed to make batch jobs work. It handles knowing how to store the batch job details for running later. And the ability to run is on a recurrence. And many of the other things that are common to all batch jobs. This allows you to just focus on the code that your particular batch job needs to run.

There are two ways of creating a RunBaseBatch batch job.

Create The Class And Extend RunBaseBatch

First, create a project in Visual Studio.

Then, after setting the model of the project, right click on the project, and select Add>New Item.

Next, select Class as the object type, and provide a name.

As a best practice, the name of a batch job usually ends with the suffix “Batch”. I named mine “MyBatchJobBatch”.

Double click on the class object to open the code editor window.

Finally, add the text “extends RunBatchBatch” after the text “class <The name of your batch class>”.

This uses what is called inheritance. For those of you who may be new to this concept, here is overly simplified explanation. By us adding this text, the class we just created will ‘inherit‘ all of the code, variables, and functionality of the RunBaseBatch class.

We have now created the start of a batch job. But there is still a lot of methods we need to add to actually make it work. So that leads me to second way to create a D365 batch job.

Duplicate an Existing Class that Extends RunBaseBatch

Of of the my favorite ways of learning something is to follow an example. Therefore, instead of creating a new Batch Job class from scratch, we can make our lives a lot easier by duplicating an existing one. Then modifying it for our needs.

To begin, in the Application Explorer in Visual Studio, search for MCRFTCEventProcessBatch. You will see a few results.

Next, right click on the class ‘MCRFTCEventProcessBatch’, and select ‘Duplicate in Project’.

A new class will be added to your project named ‘MCRFTCEventProcessBatchCopy’.

Before opening it, let’s go ahead and delete the first class created named ‘MyBatchJobBatch’ by selecting it and pressing the delete key. Next, right click on ‘MCRFTCEventProcessBatchJob’, and select ‘rename’. Enter in a new name such as ‘MyBatchJobBatch’. Finally, double click on it to open the class in a code editor window.

I have been duplicating this class since before it was even part of the base Microsoft product.

Batch Job Methods

In order to make the batch job actually do something, there are a number of methods that need to be overridden or implemented from the base class. I will explain the ones listed in this example. However, some of them are optional.

Main Method

To start, scroll down to the ‘main’ method. This method is called when the user clicks the Menu Item that points to the Batch job.

Specifically, after creating a batch job class, you need to create a Menu Item, and set the Object Type to ‘Class’, and Object to the name of the class. See the ‘MCRFTCEventProcessBatch’ Action Menu Item, as an example.

In this example, the main method looks like this:

    public static void main(Args args)
    {
        MCRFTCEventProcessBatch mcrFTCEventProcessBatch = new MCRFTCEventProcessBatch();
        mcrFTCEventProcessBatch.parmInBatch(false);

        if (mcrFTCEventProcessBatch.prompt())
            mcrFTCEventProcessBatch.runOperation();
    }

There are several things the main method needs to do.

  1. It needs to instantiate the class. See “MCRFTCEventProcessBatch mcrFTCEventProcessBatch = new MCRFTCEventProcessBatch”. Replace each instances of the name of the batch class with the name of your batch class. This line often calls the ‘construct’ method instead of ‘new’ method, to follow best practices.
  2. The code needs to have “if (<name of batch class variable>.prompt())”. The prompt method is what causes the dialog form to open.
  3. The code needs to have “<name of batch class variable>.runOperation();”. This method ultimately will call the ‘run’ method in your class. Which will perform the task your batch job was created to do.

The adjusted code for example looks like this:

public static void main(Args args)
    {
        MyBatchJobBatch myBatchJobBatch = new myBatchJobBatch();
        myBatchJobBatch.parmInBatch(false);

        if (myBatchJobBatch.prompt())
            myBatchJobBatch.runOperation();
    }

Note: The line “myBatchJobBatch.parmInBatch(false);” is optional. It tells the system whether the ‘Run in Batch’ radio button should be set by default or not. A user can change the value after the form is opened; in most cases.

Run Method

Next, locate the ‘run’ method in your class. The purpose of this method is to perform the task or process that your batch job should do. There are not any requirements relating to this method. However, typically this class will look through a Query object that was created in the Application Explorer. Or, it will have a while select statement that will loop through records.

Any info, warning, or error messages, that are part of the code in the ‘run’ method will display to the screen if the batch job is run not in batch. Otherwise, these messages will be saved and can be viewed by looking at the batch job logs.

Common Patterns

Again, while there are no ‘rules’ relating to what code is run in a batch job, there are a few common design patterns.

Ideally, when a batch job is run, it should perform some action, such that if the same batch job is run again, it would not process the same records a second time. This would likely be a waste of resources.

One common pattern is this. D365 Batch jobs will often loop through all records with a given status, and change the status after a process is performed on that record. This way, the same records will not be processed twice.

A second pattern is store a ‘last run date and time’ in a field on a table. And then, to process all records with a modified datetime that is more recent than the last run date and time.

It is common for batch jobs to be created for the purpose of cleaning up or deleting outdated data. Such as logs or integration messages that have already been processed. In those cases, there is a third common pattern. For example, you may want to delete processed records that are older than 30 days. When the batch job runs, the ‘run’ method, code can be added to calculate the date time 30 days before the current date and time. Then, the system can loop through all records with a created datetime that is older than that, and delete the records.

Following this model allows for batch jobs to be run frequently or infrequently, yet still only process what is needed once.

Parameters

Often, users will want to control want to be able to provide input on what records are processed. For example, by giving a start and end date. Or a value of a field that should be used to filter the records to be processed. Or even the number of days in the past to keep data.

In our specific example, the class MCRFTCEventProcessBatch has two parameters. A startDate and an endDate. These parameters can be entered by a user, and then they are used by the code in the ‘run’ method.

The next several methods will show what code is needed to add parameters to a D365 batch job.

Class Variables

Although class variables can be added anywhere in a class, it is a best practice to add them at the top, right after the class definition.

Scroll to the top of the method, and locate this code:

    DialogField         dlgStartDate;
    DialogField         dlgEndDate;

    transDate           startDate;
    transDate           endDate;

In this example, this code adds two variables for storing the startDate and endDate that the user passed in via the dialog form. And two variables for storing the dialog ‘controls’ themselves. I will explain further in just a minute.

Dialog method

Next, locate the Dialog method. In this example, it looks like this:

    public Object dialog()
    {
        DialogRunbase               dialog;

        //Setup the dialog
        dialog = super();
        dialog.caption("@MCR12442");

        dlgStartDate = dialog.addField(extendedtypestr(transDate), "@SYS35856" );
        dlgStartDate.value('');

        dlgEndDate = dialog.addField(extendedtypestr(transDate), "@SYS22882" );
        dlgEndDate.value('');

        return dialog;
    }

This method is responsible for adding controls to the dialog form that is shown to the user. First, the caption on the dialog form is set.

Next, a field is added that uses the extended data type ‘transDate’. As well as sets the label on the control to ‘@SYS35856’, which is ‘Start date’. The ‘transDate’ extended data type has some special properties that allow it to show a calendar pop-up when clicked on. The control itself is referenced by the class variable named ‘dlgStartDate’.

Then, another transDate field is added, but this time the label is ‘End date’, and the control is stored in the variable dlgEndDate.

In the case of both controls, the default value is set to blank.

Finally the ‘dialog’ class object is returned to the calling code, which in this case is RunBaseBatch.

While this may seem a bit complicated at first, this code really does make things much easier. This allows a developer to only add code relating to the parameters they need. They do not need to create a whole new form in the Application Explorer. They can do it here with a few lines of code. And the base class RunBaseBatch handles the rest.

This method is only needed if your batch job uses parameters. Otherwise, you can delete this method entirely.

GetFromDialog Method

Correspondingly, the getFromDialog is called after the user presses the ‘ok’ button on the dialog form. It is responsible for reading the values out of the dialog controls, and storing them into class variables. The class variables can then be used by the ‘run’ method.

In our example, the code looks like this:

public boolean getFromDialog()
    {
        boolean ret;

        ret = super();

        startDate   = dlgStartDate.value();
        if (!startDate)
        {
            startDate = DateTimeUtil::date(DateTimeUtil::minValue());
        }

        endDate     = dlgEndDate.value();
        if (!endDate)
        {
            endDate = DateTimeUtil::date(DateTimeUtil::maxValue());
        }

        return ret;
    }

The code dlgStartDate.value() gets the date value from the control. Next, it is assigned to the ‘startDate’ class variable. There is additional code, that makes it so if the user leaves the control blank, the minimum and maximum date value will be used.

In your situation, you should change this method to retrieve the values out of the controls you added in the ‘dialog’ method.

Pack, Unpack, a Macro

Whenever a D365 batch job runs in batch, and it has parameters, the system needs to store the values the user entered as parameters. That way, the system can use them again, when the batch job recurrence causes it to run.

Specifically, each time the batch job is run, the system will read the parameter values from the Batch.Parameters field, where they are stored. This field is a container field, which means it can contain a list of several values of different types of data. This is very cool. But the system needs to know how to store the data. And how to the pull it back out and put it class variables, for the ‘run’ method to use.

This is where the pack, unpack and macro at the top of the class come into use.

Macro

At the very top of the class you will see this code:

    #define.CurrentVersion(2)
    #localmacro.CurrentList
        dummy,
        startDate,
        endDate
    #endmacro

This is what is called a Macro. I won’t get into this in depth here. But essentially this tells the compiler to replace any code that reads #CurrentVersion with ‘2’. And any code that ready #CurrentList with ‘dummy, startDate, endDate’.

For your situation replace, the text ‘dummy, startDate, endDate’, which a list of your class variables used to store parameter values. If your batch job does not have any parameters values, just leave the text ‘dummy’, as one variable is required.

Pack

Next, navigate to the pack method in the code editor. The code looks like this:

    public container pack()
    {
        return [#CurrentVersion, #CurrentList];
    }

This method will look exactly the same for basically every Batch Job class. So you can just leave it along, and not change it.

That said, if you want an explanation, here it is. This code returns a variable of type container. And that container object will contain the value associated with the macro #CurrentVersion. (In our case the number 2). Additionally, the container will contain all the variable values you listed in the #CurrentList macro.

Therefore, whether you included 2 parameters, or 20 parameters in your batch job class, this is a very generic way of storing all of those values in a ‘container’ variable.

Again, this variable will be written to the Batch.Parameters table, and stored for when the batch job is actually run by the system.

Unpack

In like manner, navigate to the unpack method in the code editor. The code looks like this:

    public boolean unpack(container packedClass)
    {
        Version version = runbase::getVersion(packedClass);
        switch (version)
        {
            case #CurrentVersion:
                [version, #CurrentList] = packedClass;
                break;
            default:
                return false;
        }

        return true;
    }

Similarly, this code should look the same for every batch job you write. Therefore, you can leave it unchanged. But it does need to exist.

This code is a little more complicated to understand. But when the batch job is run, the system will read the value from Batch.Parameters field, which is of type container, and pass it into this method. This method will take each variable that is in the ‘container’, and use this method to set all of the class variables. Those class variables are then used in the ‘run’ method.

The idea again is that it doesn’t matter if we are storing 2 variables or 20 variables. As long as we store them in the store order, and then pull them back out and assign them in the same order, the class variables will get set correctly.

While there is a little bit more explanation to this method, this is the main thing most people need to understand.

Description

The description method is used to set the default task description of this D365 batch job. This text is shown in a field in the dialog form. This text will be saved as shown as the ‘job description’ in the Batch Jobs form.

The task description text can be modified by a user to be different than the default value. This can help differentiate two batch jobs instances from each other. Particularly when they were given different parameters or recurrence options.

The code looks like this:

public static ClassDescription description()
    {
        return "@MCR12442";
    }

Users should always have a description method. But they should change the label being return to match the description of their batch job.

Other Methods

There are additional methods can be implemented in the batch job class. Therefore, the methods I mention here are optional. And you do not need to add them in you do not need them.

The validate method is called before the ‘run’ method is called and can be used to validate data or settings before the process is run.

    public boolean validate(Object calledFrom = null)
    {
        if (!MCROrderParameters::find().MCRFTCProcessing)
        {
            return checkFailed("@MCR12705");
        }

        return true;
    }

The showQueryValues method will show the ‘Select’ button when a Query object is used by the batch class. This allows users to add additional ranges to the filters, which is very powerful.

    public boolean showQueryValues()
    {
        return true;
    }

The canGoBatch is a method that controls whether you are allowed to run the process in the background, or if the process can only be run by the user.

    public boolean canGoBatch()
    {
        return true;
    }

There are still other methods such as canRunInNewSession, and isRetryable.

Adding A Menu Item

After creating a D365 batch job, create a Menu Item that points to the batch job class you just created. As a best practice, all Menu Items that call batch jobs should be Action Menu Items.

Similarly, I recommend you just duplicate the MCRFTCEventProcessBatch Action Menu Item. Then rename it, and change the ‘Object’ property and Lable.

Next, add your Menu Item to a Menu, by extending an existing Menu.

Lastly, create or extend security objects, and add your Menu Item to a Priviledge.

Conclusion

In this article I explained how D365 batch jobs can offload work to the server, and improve the user experience, by allowing them to not have to wait. I then showed you how to add ‘extends RunBaseBatch’ to a class to make it into a batch job class. Finally, we looked at the core methods that are needed to make a batch job function. And how many of them can just be copied and pasted, with some minor changes. I hope you learned something new today!

Peter Ramer
Peter Ramer is a part of the Managed Application Services team at RSM working on Microsoft Dynamics 365. He focuses on the Retail and Commerce industries. When he is not solving problems and finding ways to accelerate his clients' business, he enjoys time with his three kids and amazing wife.

Share this:

8 thoughts on “Create A D365 Batch Job

Add yours

  1. This article is very helpful Peter, is it possible to please create an article/Video to explain how parameters can be pulled using QueryRun/ShowQueryValue methods?

  2. The requirement is to create an custom table containing Account Num, Item Id , Invent batch Id and block the batch numbers which has been reserved for particular customer.

  3. Thanks for your always-helpful articles!

    What is the significance in MCRFTCEventProcessBatch of having the processEvent method defined within the run method?

Leave a Reply

Your email address will not be published. Required fields are marked *

Proudly powered by WordPress | Theme: Baskerville 2 by Anders Noren.

Up ↑