Query-Based vs RDP SSRS Reports in D365 Finance & Operations
SSRS is still the backbone of operational and document reporting in Dynamics 365 Finance & Operations — purchase orders, invoices, statements, and most printed business documents render through it. The first design decision on any new report is also the most consequential: query-based or Report Data Provider. Pick wrong and you'll either fight the framework or rewrite it. Here's how I decide and what the full stack looks like.
Query-based vs RDP
There are two ways to feed an SSRS report in F&O.
Query-based reports bind the design directly to an AOT query. You build a query (data sources, ranges, joins) in the application explorer, point the report dataset at it, and you're done. No business logic, no X++ for data shaping.
- Use it when the data you need is a straightforward selection and join across existing tables, and the layout maps cleanly onto those columns.
- It's fast to build, easy to maintain, and gives users automatic filter ranges from the query.
- It falls apart the moment you need computed values, multi-pass aggregation, data from external sources, conditional row generation, or anything that isn't expressible as a SELECT.
Report Data Provider (RDP) reports run an X++ class that populates a temporary table, and the report reads from that temp table.
- Use it whenever you need real business logic: calculations, looping, calling existing classes, formatting that depends on runtime conditions, or assembling rows from several sources.
- It's the right choice for documents (invoices, confirmations) where you typically have a header/line structure and need to apply number sequences, currency conversion, or print settings.
- More moving parts, but it's the only honest option once logic is involved.
My default: if I can't express the requirement as one query in under a minute, it's an RDP.
The RDP component stack
A full RDP report is a set of cooperating artifacts. Skip one and it won't run:
- Temporary table — the shape of the data the report renders. Set its table type to
TempDB(or InMemory for small sets). Every report column comes from a field here. - Data contract class — a parameter container decorated with
DataContractAttribute. Its parm methods (each markedDataMemberAttribute) become the report parameters the user fills in. - RDP (data provider) class — extends
SRSReportDataProviderBase(orSrsReportDataProviderPreProcessfor pre-processed). It reads the contract, does the work, and inserts into the temp table. - Controller class — extends
SrsReportRunController. Optional but recommended; it lets you set the report name, pre-process, override the dialog, and control which design runs. The menu item points at the controller. - UI Builder class (optional) — extends
SrsReportDataContractUIBuilderwhen you need to customize the parameter dialog: group fields, add lookups, run logic when a value changes. - Report design — built in the model in Visual Studio, bound to the temp table dataset.
- Output menu item — the entry point a user or a button invokes; it references the controller (or the report directly).
The wiring order matters. The menu item calls the controller, the controller resolves the contract and runs the RDP, the RDP fills the temp table, and SSRS renders the design from that table.
An RDP class and data contract sketch
Here's the minimal skeleton I start from. First the contract:
[DataContractAttribute]
public class CustOpenBalanceContract
{
TransDate asOfDate;
CustGroupId custGroup;
[DataMemberAttribute("AsOfDate")]
public TransDate parmAsOfDate(TransDate _date = asOfDate)
{
asOfDate = _date;
return asOfDate;
}
[DataMemberAttribute("CustGroup")]
public CustGroupId parmCustGroup(CustGroupId _group = custGroup)
{
custGroup = _group;
return custGroup;
}
}
Then the data provider. The SRSReportParameterAttribute ties the contract to this class so the framework knows the parameter shape:
[SRSReportParameterAttribute(classStr(CustOpenBalanceContract))]
public class CustOpenBalanceDP extends SRSReportDataProviderBase
{
CustOpenBalanceTmp tmp; // the temp table
[SRSReportDataSetAttribute(tableStr(CustOpenBalanceTmp))]
public CustOpenBalanceTmp getCustOpenBalanceTmp()
{
select tmp; // return the populated temp table
return tmp;
}
public void processReport()
{
CustOpenBalanceContract contract = this.parmDataContract();
TransDate asOf = contract.parmAsOfDate();
CustGroupId group = contract.parmCustGroup();
CustTable custTable;
while select custTable
where custTable.CustGroup == group
{
tmp.clear();
tmp.AccountNum = custTable.AccountNum;
tmp.Name = custTable.name();
tmp.Balance = custTable.openBalanceMST(asOf); // real logic
tmp.insert();
}
}
}
processReport is the method the framework calls; getCustOpenBalanceTmp is the dataset method SSRS binds to. The temp table buffer field names must match what the design expects.
Precision design vs auto design
Two ways to lay out the report:
- Auto design — the framework arranges the dataset into a table/matrix automatically. Quick, good for tabular operational reports and list output. Limited control over exact positioning.
- Precision design — full pixel-level control, built in the SSRS designer (RDL). This is what you use for any document that must look a specific way: invoices, checks, statements, anything customer-facing or regulated.
Rule: list/analytical output, auto design; pixel-exact business documents, precision design.
Parameters and the UI Builder
Out of the box, each DataMember in the contract becomes a dialog field, typed automatically (a date member gets a date picker). When the default dialog isn't enough, the UI Builder takes over: you override build() to add field groups, attach a custom lookup to a parameter, set default values, or wire a modified handler so changing one field filters another. A common case is replacing a plain string parameter with a proper lookup against a table so users pick a valid value rather than typing.
Printing and print management
For business documents, F&O layers print management on top of SSRS. It governs destination (screen, printer, email, file), number of copies, original vs copy designations, and per-document-type overrides at company, customer, or vendor level. Document reports (PO, sales invoice, packing slip) hook into print management so a user can configure, for example, "email the original to this customer and print two copies to the warehouse printer" without touching the report. When you build a document RDP, route it through the print management framework rather than hardcoding the destination — otherwise you lose all of that configurability and the business will ask for it within a week.
Start with the temp table and contract, get the RDP populating data you can verify with a quick job, then build the design last. Designing the layout before the data exists is the most common way I see these reports stall.
Keep reading
Extending Data Entities in D365 Finance & Operations Without Breaking Upgrades
Add fields, computed columns, and validation to standard D365 Finance & Operations data entities the upgrade-safe way — with X++ examples and the staging-table traps to avoid.
Chain of Command vs Event Handlers: Extending D365 F&O the Right Way
When to use Chain of Command and when to use pre/post event handlers in Dynamics 365 Finance & Operations — with X++ examples, a decision table, and the gotchas that trip up teams.
Electronic Reporting in D365 Finance: Building Custom Formats Without Code
A practical guide to the Electronic Reporting (ER) framework in D365 Finance — data models, model mappings, and format configurations to produce custom files without X++.
Newsletter
New posts, straight to your inbox
One email per post. No spam, no tracking pixels, unsubscribe anytime.
Comments
No comments yet. Be the first.