11.2 Report Data Providers & Contracts
Key Takeaways
- The SSRS reporting architecture in Dynamics 365 F&O follows a decoupled Model-View-Controller (MVC) pattern comprising the Report Data Contract (parameter model), Report Controller (orchestrator), UI Builder (dialog view), Report Data Provider (data processing engine), and SSRS Report Design (presentation view).
- The Report Data Provider (RDP) class extends SRSReportDataProviderBase and must be decorated with [SRSReportParameterAttribute(classStr(MyContract))] to receive user runtime parameters and optionally [SRSReportQueryAttribute(queryStr(MyQuery))] to bind static AOT queries.
- Report data processing logic executes exclusively within the processReport() method of the RDP class, querying operational tables and populating staging tables exposed to SSRS via accessor methods decorated with [SRSReportDataSetAttribute(tableStr(MyTmpTable))].
- Staging table type selection is a critical performance determinant: TempDB staging tables reside in SQL Server tempdb, support clustered/non-clustered indexes, execute high-speed set-based insert_recordset operations, and avoid the crippling cross-tier memory serialization bottlenecks of InMemory tables.
- The Report Controller class, extending SrsReportRunController, manages the report lifecycle, enables batch execution, overrides report parameters programmatically via preRunModifyContract(), and binds caller record contexts (Args.record()) to report contracts.
11.2 Report Data Providers & Contracts
Quick Answer: The Report Data Provider (RDP) framework in Dynamics 365 F&O adapts the Model-View-Controller (MVC) design pattern for operational document generation. The Data Contract class (
[DataContractAttribute]) acts as the parameter model; the UI Builder class (SrsReportDataContractUIBuilder) customizes the dialog view; the Controller class (SrsReportRunController) coordinates report execution and context injection; and the RDP class (SRSReportDataProviderBase) executes business logic inprocessReport(). The RDP class binds to contracts via[SRSReportParameterAttribute(classStr(MyContract))]and exposes staging table datasets via[SRSReportDataSetAttribute(tableStr(MyTmpTable))]. For report staging tables, developers must chooseTempDBoverInMemorybecause TempDB resides in the SQL engine, supports indexes, utilizes fast set-basedinsert_recordsetoperations, and eliminates the severe cross-tier memory serialization bottlenecks that crash large reports.
1. The SSRS Architectural Framework: F&O Reporting MVC Pattern
SQL Server Reporting Services in Dynamics 365 Finance and Operations relies on a strictly decoupled, object-oriented framework. Rather than embedding ad-hoc SQL queries directly inside report definition files (.rdl), F&O separates concerns across specialized X++ classes and metadata objects:
┌─────────────────────────────────────────────────────────────────────────────┐
│ F&O Reporting Pattern │
├─────────────────┬───────────────────────────────────────────────────────────┤
│ Component │ Architectural Role & Base Class │
├─────────────────┼───────────────────────────────────────────────────────────┤
│ Data Contract │ Parameter container decorated with [DataContractAttribute]│
│ UI Builder │ Custom dialog renderer extending SrsReportDataContractUI..│
│ Controller │ Execution coordinator extending SrsReportRunController │
│ RDP Class │ Data extraction engine extending SRSReportDataProviderBase│
│ Staging Table │ Data persistence buffer (TempDB table) │
│ SSRS Design │ Visual presentation layout (.rdl) in Visual Studio │
└─────────────────┴───────────────────────────────────────────────────────────┘
Execution Lifecycle Sequence
- Invocation: The user clicks an Output Menu Item, or an automated process calls the report. The Menu Item points directly to the Report Controller class.
- Initialization: The Controller instantiates the Data Contract and passes it to the UI Builder to render the runtime parameter dialog.
- User Input / Pre-Run Modification: The user enters parameter values. Alternatively, the Controller inspects the calling context (
Args.record()) inpreRunModifyContract()and sets contract values programmatically without user interaction. - Execution Dispatch: The Controller submits the report execution payload to the batch subsystem or executes synchronously. The reporting engine invokes the RDP class on the AOS.
- Data Processing: The RDP's
processReport()method executes: it reads parameters from the contract, queries business tables, applies financial logic, and inserts processed rows into the temporary staging table. - Dataset Streaming: The SSRS processing service retrieves the populated staging table buffer via the RDP accessor method and renders the final design (PDF, Screen, Excel, or Printer).
2. Report Data Contract & UI Builder Classes
The Report Data Contract
The Data Contract class defines the parameters required by the report. It encapsulates input values such as date ranges, customer accounts, financial dimensions, or boolean flags.
[DataContractAttribute]
public class CustInvoiceReportContract implements SysOperationValidatable
{
private CustAccount custAccount;
private FromDate fromDate;
private ToDate toDate;
private boolean includeZeroBalance;
[DataMemberAttribute('CustAccount'),
SysOperationLabelAttribute(literalStr("@SYS7149")),
SysOperationHelpTextAttribute(literalStr("@SYS7149")),
SysOperationDisplayOrderAttribute('1')]
public CustAccount parmCustAccount(CustAccount _custAccount = custAccount)
{
custAccount = _custAccount;
return custAccount;
}
[DataMemberAttribute('FromDate'),
SysOperationLabelAttribute(literalStr("@SYS5209")),
SysOperationDisplayOrderAttribute('2')]
public FromDate parmFromDate(FromDate _fromDate = fromDate)
{
fromDate = _fromDate;
return fromDate;
}
[DataMemberAttribute('ToDate'),
SysOperationLabelAttribute(literalStr("@SYS35904")),
SysOperationDisplayOrderAttribute('3')]
public ToDate parmToDate(ToDate _toDate = toDate)
{
toDate = _toDate;
return toDate;
}
[DataMemberAttribute('IncludeZeroBalance'),
SysOperationLabelAttribute(literalStr("@SYS12345")),
SysOperationDisplayOrderAttribute('4')]
public boolean parmIncludeZeroBalance(boolean _includeZeroBalance = includeZeroBalance)
{
includeZeroBalance = _includeZeroBalance;
return includeZeroBalance;
}
public boolean validate()
{
boolean isValid = true;
if (fromDate && toDate && fromDate > toDate)
{
isValid = checkFailed("From Date cannot be later than To Date.");
}
return isValid;
}
}
[DataContractAttribute]: Instructs the WCF/SysOperation serialization framework that this class represents a data contract.[DataMemberAttribute('ParameterName')]: Decorates parm methods to expose them as serializable members. The name passed toDataMemberAttributebecomes the parameter name inside the SSRS report definition.SysOperationValidatable: Implementing this interface allows developers to override thevalidate()method to enforce business validation before report processing commences.
The UI Builder Class
When a report parameter dialog requires dynamic behavior—such as custom lookup forms, conditionally hiding or disabling fields based on another parameter, or interdependent cascading lookups—developers extend SrsReportDataContractUIBuilder.
- Binding to Contract: Decorate the contract class with
[SysOperationContractProcessingAttribute(classStr(CustInvoiceReportUIBuilder))]. build()Method: Overridden to modify dialog layout controls.postBuild()Method: Overridden to register runtime event overrides, such as lookup methods (registerOverrideMethod(methodStr(CustInvoiceReportUIBuilder, custAccountLookup), this)).
3. Report Data Provider (RDP) Class Implementation
The RDP class is the data extraction engine. It executes on the Application Object Server (AOS) tier and transforms complex normalized ERP schema data into flat, report-ready denormalized rows.
Class Declaration & Attributes
[SRSReportParameterAttribute(classStr(CustInvoiceReportContract)),
SRSReportQueryAttribute(queryStr(CustInvoiceReportQuery))]
public class CustInvoiceReportDP extends SRSReportDataProviderBase
{
private CustInvoiceReportTmp custInvoiceReportTmp;
[SRSReportDataSetAttribute(tableStr(CustInvoiceReportTmp))]
public CustInvoiceReportTmp getCustInvoiceReportTmp()
{
select * from custInvoiceReportTmp;
return custInvoiceReportTmp;
}
public void processReport()
{
CustInvoiceReportContract contract = this.parmDataContract() as CustInvoiceReportContract;
Query query = this.parmQuery();
CustAccount custAccount = contract.parmCustAccount();
FromDate fromDate = contract.parmFromDate();
ToDate toDate = contract.parmToDate();
// Set dynamic query ranges from contract parameters
QueryBuildDataSource qbdsCustTable = query.dataSourceTable(tableStr(CustTable));
if (custAccount)
{
qbdsCustTable.addRange(fieldNum(CustTable, AccountNum)).value(queryValue(custAccount));
}
QueryRun queryRun = new QueryRun(query);
while (queryRun.next())
{
CustTable custTable = queryRun.get(tableStr(CustTable));
custInvoiceReportTmp.clear();
custInvoiceReportTmp.AccountNum = custTable.AccountNum;
custInvoiceReportTmp.CustName = custTable.name();
custInvoiceReportTmp.CreditMax = custTable.CreditMax;
custInvoiceReportTmp.CurrencyCode = custTable.Currency;
custInvoiceReportTmp.insert();
}
}
}
Mandatory RDP Rules & Attributes
- Base Class: Must extend
SRSReportDataProviderBase. - Parameter Binding: Decorated with
[SRSReportParameterAttribute(classStr(MyContract))]. This enables the reporting framework to inject the deserialized contract instance accessible viathis.parmDataContract(). - Query Binding (Optional): Decorated with
[SRSReportQueryAttribute(queryStr(MyQuery))]. Allows developers to attach a predefined AOT query whose ranges can be manipulated at runtime. - Data Processing: All business logic must reside within
processReport(). - Dataset Accessor: Must expose a public getter method returning the temporary table buffer decorated with
[SRSReportDataSetAttribute(tableStr(MyTmpTable))]. SSRS discovers available report fields by inspecting this attribute.
4. Report Controller Classes (SrsReportRunController)
The Controller class coordinates the report's execution pipeline. In professional enterprise implementations, developers never link menu items directly to reports; they always link menu items to a Controller class.
Primary Controller Responsibilities
- Extending
SrsReportRunController. - Overriding
preRunModifyContract()to inspect caller records fromArgsand populate the contract dynamically. - Setting report design dynamically (e.g., selecting different country-specific designs based on company localization).
- Controlling whether the runtime dialog prompt appears (
this.parmShowDialog(false)).
public class CustInvoiceReportController extends SrsReportRunController
{
public static void main(Args _args)
{
CustInvoiceReportController controller = new CustInvoiceReportController();
controller.parmReportName(ssrsReportStr(CustInvoiceReport, PrecisionDesign));
controller.parmArgs(_args);
controller.startOperation();
}
protected void preRunModifyContract()
{
super();
if (this.parmArgs() && this.parmArgs().record())
{
CustTable custTable = this.parmArgs().record() as CustTable;
if (custTable)
{
CustInvoiceReportContract contract = this.parmReportContract().parmRdpContract() as CustInvoiceReportContract;
if (contract)
{
contract.parmCustAccount(custTable.AccountNum);
}
}
}
}
}
5. Report Staging Tables: TempDB vs. InMemory vs. Regular
The choice of table type for the RDP staging table is one of the most heavily tested performance topics on the MB-500 exam.
Staging Table Architectural Comparison
| Feature / Metric | TempDB Table | InMemory Table | Regular Table |
|---|---|---|---|
| Physical Location | Microsoft SQL Server tempdb database | AOS Server Memory (RAM); spills to local disk if > 128 KB | Primary Database (AxDB) transactional storage |
| Serialization Cost | Zero cross-tier memory copy — SSRS queries tempdb directly | Extremely High — Serialized via RPC/WCF from AOS to SSRS | Moderate disk I/O; requires explicit transactional cleanup |
| Indexing Capability | Supports both clustered and non-clustered indexes | Supports primary/alternate indexes in memory only | Full indexing support |
| Set-Based Operations | Fully Supported (insert_recordset, update_recordset) | Unsupported — Rows inserted row-by-row in memory | Fully Supported |
| High-Volume Scalability | Scales to millions of rows efficiently | Degrades severely; crashes with OutOfMemory exceptions | Scales, but causes severe table bloat and locking |
| Session Isolation | Automatic session boundary managed by SQL Server | Isolated to current tier process memory | None — Requires manual CreatedTransactionId filtering |
Why TempDB is Mandatory for Enterprise High-Volume Reports
- Elimination of Cross-Tier Serialization: When an RDP uses an
InMemorytable, the AOS process must serialize the entire in-memory dataset across the network boundary to the SSRS reporting service via WCF. For a 100,000-line invoice report, this serialization consumes gigabytes of AOS RAM, exhausts network bandwidth, and frequently throws timeout exceptions. WithTempDB, the table exists physically within SQL Server'stempdb. The SSRS service executes a direct SQL query againsttempdb, completely bypassing cross-tier memory serialization. - Set-Based Performance:
TempDBtables support set-based SQL operations. Instead of writing slow, row-by-rowwhile selectloops withtmpTable.insert(), developers can execute high-speedinsert_recordsetstatements that push the entire data population workload down into the database engine in a single round-trip. - Clustered Index Optimization: Developers can create physical clustered indexes on
TempDBtables matching theGROUP BYand sorting orders used by the SSRS report tablix controls, dramatically accelerating report rendering.
[!IMPORTANT] TempDB Session Isolation Rule:
TempDBtables created by an RDP class are automatically dropped when the report execution context finishes. To ensure absolute data isolation across concurrent user batch sessions, the reporting framework binds temporary table instances to the unique execution transaction (CreatedTransactionId).
6. Scenario Walk-Through: Optimizing a Stalled Customer Statement Report
Scenario: High-Volume Month-End Billing Bottleneck
Contoso Wholesale runs a monthly Customer Account Statement report for 45,000 customers. In the legacy implementation, the report takes 4.5 hours to run and frequently terminates with an AOS Client out of memory exception during peak billing.
Root Cause Analysis:
- The staging table
CustStatementTmpwas configured asTableType = InMemory. - The RDP class used nested
while selectloops in X++, inserting records one by one. - Serializing 350,000 records from AOS memory to the SSRS report microservice caused the AOS worker process to exceed its memory threshold.
Refactoring Strategy:
- Change
CustStatementTmp.TableTypefromInMemorytoTempDB. - Add a clustered index on
CustStatementTmpon(AccountNum, TransDate, Voucher). - Replace the row-by-row iteration in
processReport()with a singleinsert_recordsetjoiningCustTableandCustTransdirectly. - Outcome: Execution time drops from 4.5 hours to 6 minutes, memory consumption on the AOS drops by 94%, and timeout errors are completely eliminated.
7. Real-World Exam Traps: RDP & Contracts
[!WARNING] Exam Trap 1: Using InMemory Tables for Multi-Page or High-Volume Reports The MB-500 exam frequently describes an SSRS report that fails with memory throttling or timeout errors when processing large datasets. The answer is almost always to change the staging table's
TableTypeproperty fromInMemorytoTempDB.
[!WARNING] Exam Trap 2: Omitting the
[SRSReportDataSetAttribute]Decorator If a developer creates a public accessor method on the RDP class returning the temporary table buffer but forgets to decorate it with[SRSReportDataSetAttribute(tableStr(MyTmpTable))], the table will not appear in Visual Studio when adding a dataset to the report definition. The attribute is mandatory for metadata discovery.
[!WARNING] Exam Trap 3: Putting Dialog Manipulation Code in the Contract or RDP Never attempt to override lookups or modify dialog control properties inside the Data Contract or Report Data Provider classes. Dialog UI customization belongs exclusively in the UI Builder class (
SrsReportDataContractUIBuilder), while pre-run parameter overrides belong in the Controller class (preRunModifyContract()).
[!WARNING] Exam Trap 4: Forgetting
super()in Controller Lifecycle Methods When overridingpreRunModifyContract()orprePromptModifyContract()in a class extendingSrsReportRunController, you must always callsuper(). Omittingsuper()breaks contract initialization and parameter deserialization.
Which class-level attribute is mandatory on an X++ Report Data Provider (RDP) class to bind it to a Report Data Contract containing runtime parameters?
A developer is creating a high-volume operational SSRS report in Dynamics 365 F&O that will process over 200,000 ledger rows per execution. Which staging table type should be selected, and why?
In the Dynamics 365 F&O SSRS framework, where should a developer place code to inspect caller record context (Args.record()) and dynamically set report parameters before execution without user intervention?
What class and method attribute combination is required to define a serializable report parameter inside a custom Report Data Contract?