SSRS report in D365 F&O with RDP and Contract classes
Build an SSRS report in D365 F&O: temp table, Contract class, RDP class, controller and menu item. Steps, sample X++ code and common mistakes.
The short answer
A custom SSRS report in Dynamics 365 Finance & Operations relies on four objects: a temporary table holding the dataset, a Contract class defining the parameters the user enters, an RDP (Report Data Provider) class filling the temporary table in X++, and the report itself, whose dataset points to the RDP class. A controller and an output menu item complete the set so the report can be run from the application.
To change a standard report, the approach differs: you cannot extend an RDP class directly. You add fields through a temporary table extension, populate them with an event handler and redirect the menu item to a custom design.
Step 1: the temporary table and the Contract class
Create a table (here MyCustBalanceTmp) with the TableType property set to TempDB and the fields the report will show. Microsoft recommends TempDB for reports that process a lot of data (see step 4).
The Contract class holds the parameters. Each parameter is a parm method decorated with DataMemberAttribute; the class carries DataContractAttribute. The framework builds the dialog automatically from these members.
[DataContractAttribute]
class MyCustBalanceContract
{
CustGroupId custGroupId;
[DataMemberAttribute('CustGroupId')]
public CustGroupId parmCustGroupId(CustGroupId _custGroupId = custGroupId)
{
custGroupId = _custGroupId;
return custGroupId;
}
}Step 2: the RDP class
The RDP class extends SrsReportDataProviderBase. The SRSReportParameterAttribute attribute links it to the Contract class; a method decorated with SRSReportDataSetAttribute returns the temporary table to the report server; business logic goes in processReport, called at run time. If the report starts from an AOT query the user can change, add SRSReportQueryAttribute and read it with parmQuery().
[SRSReportParameterAttribute(classStr(MyCustBalanceContract))]
class MyCustBalanceDP extends SrsReportDataProviderBase
{
MyCustBalanceTmp custBalanceTmp;
[SRSReportDataSetAttribute(tableStr(MyCustBalanceTmp))]
public MyCustBalanceTmp getMyCustBalanceTmp()
{
select custBalanceTmp;
return custBalanceTmp;
}
public void processReport()
{
MyCustBalanceContract contract = this.parmDataContract() as MyCustBalanceContract;
CustGroupId custGroupId = contract.parmCustGroupId();
CustTable custTable;
while select custTable
where custTable.CustGroup == custGroupId
{
custBalanceTmp.clear();
custBalanceTmp.AccountNum = custTable.AccountNum;
custBalanceTmp.Balance = custTable.balanceMST();
custBalanceTmp.insert();
}
}
}Here, balanceMST() is called row by row: fine for one customer group, to avoid on large volumes. In that case prefer set-based operations (insert_recordset) or a query-side calculation.
Step 3: the report, the controller and the menu item
- In Visual Studio, add a report to the project (here MyCustBalance). Create a dataset, choose the "Report Data Provider" data source type and select MyCustBalanceDP: the temporary table fields and the Contract parameters appear.
- Draw the design (here "Report"): an auto design for a simple report, a precision design for a formatted document.
- Add a controller if you need to prepare arguments or pick the design at run time. The pattern below follows the Microsoft Learn example.
- Create an output menu item pointing to the controller (or straight to the report), add it to a privilege, then deploy the report.
class MyCustBalanceController extends SrsReportRunController
{
public static void main(Args _args)
{
MyCustBalanceController controller = new MyCustBalanceController();
controller.parmReportName(ssrsReportStr(MyCustBalance, Report));
controller.parmArgs(_args);
controller.startOperation();
}
}Step 4: long-running reports and changing a standard report
Interactive connections are limited to 10 minutes. For a report that can exceed this, Microsoft Learn describes switching to pre-processing: replace SrsReportDataProviderBase with SrsReportDataProviderPreProcessTempDB, set the table to TempDB, rebuild the RDP class, restore the dataset in the designer, redeploy, then point the menu item to a controller. Batch processing is the other lever.
To enrich a standard report without over-layering, Learn describes two methods: a post-handler on the RDP class's processReport (bulk update of the temporary table, once) or an Inserting event handler on the temporary table (row-by-row calculation). You duplicate the report into your model, restore the dataset and redirect the user with a PostHandler on the standard controller's construct method.
Common mistakes
- Forgetting to restore the dataset after adding a field to the temporary table: the column does not appear in the designer.
- Reading the contract without casting it to the right class: use this.parmDataContract() as MyContractClass.
- Leaving the table as InMemory for a large volume: switch to TempDB.
- Calling expensive methods row by row in processReport over thousands of records.
- Forgetting the privilege on the menu item: the report is deployed but invisible to users.
- Changing a standard report through over-layering instead of event handlers: it blocks updates.
Having the report generated instead of writing it
This table, Contract, RDP, controller pattern is repetitive: it is exactly the kind of development Skalp AI generates from your requirement. Skalp analyses the ERP context, writes the specification, produces the X++ code and unit tests, then delivers to your Azure DevOps for validation by your IT lead. Details and report examples: custom D365 F&O SSRS report. To see what AI does well or not in X++: building X++ without a consultant in 2026. Pricing: Basic, Pro and Max plans, 1 credit (≈ €400 on Basic, €200 on Pro, lower in packs) consumed only when the code is generated.
Sources
- Microsoft Learn, "Help prevent long-running reports from timing out", updated 27 Oct 2025, accessed 11 Oct 2026.
- Microsoft Learn, "Expand Application Suite report data sets", updated 22 Oct 2025, accessed 11 Oct 2026.
- Microsoft Learn, "X++ compile-time functions: ssrsReportStr" (controller example), accessed 11 Oct 2026.
- Microsoft Learn (Dynamics AX 2012, still the reference for the RDP pattern), "How to: Use a Report Data Provider Class in a Report", accessed 11 Oct 2026.
Frequently asked questions
SSRS or Power BI for a D365 F&O report?
SSRS for formatted documents to print or send (invoice, voucher, regulatory report), with document management, e-mail and scheduled batch runs. Power BI for interactive analysis. See also Power BI reports for D365 F&O.
Can you extend a standard RDP class?
Not directly. Microsoft Learn says to use a post-handler on processReport or an Inserting event on the temporary table, combined with an extension of that table.
Why does my SSRS report time out?
Interactive connections are limited to 10 minutes. Move the RDP class to SrsReportDataProviderPreProcessTempDB with a TempDB table and a controller, or run the report in batch.
Ready to transform your D365?
Join the CIOs who develop their D365 tickets in 1h for 1 credit (≈ €400 on Basic, €200 on Pro).