Query & QueryBuildDataSource in Dynamics 365 Finance & Operations
Question 12 — Building dynamic queries in X++ with ranges, joins and sorting.
Interview Question
Query, QueryBuildDataSource, QueryBuildRange and QueryRun, and
when you would use a dynamic query instead of a plain while select loop.
Model Answer (Short)
The Query framework lets you build and run queries programmatically instead of hard-coding a
while select. The key classes are: Query (the container),
QueryBuildDataSource (QBDS) (a table added to the query),
QueryBuildRange (a filter/where condition on a field),
QueryBuildFieldList (which fields to fetch), and
QueryRun (executes the query and iterates results). You use dynamic queries when the
filter criteria, data sources or sort order are not known at compile time — for example driven by
user input, a dialog, or a SysOperation contract — and because they can be surfaced to users and reused across
forms, reports and batch jobs.
Core Classes
| Class | Role |
|---|---|
Query |
The top-level container that holds one or more data sources. |
QueryBuildDataSource |
Represents a table/view added to the query; joins are added as child data sources. |
QueryBuildRange |
A filter (where condition) applied to a field of a data source. |
QueryBuildFieldList |
Controls which fields are selected (for performance / aggregation). |
QueryRun |
Executes the query and iterates the result set with next(). |
Prerequisites (Rule 5)
- Visual Studio with the Dynamics 365 developer tools.
- A custom model / package for your objects.
- Knowledge of the target table's fields and indexes for efficient ranges.
- Use
fieldNum()/tableNum()intrinsics for compile-time safety.
Code Example — Build a query with a range
Query query = new Query();
QueryBuildDataSource qbds;
QueryBuildRange qbr;
QueryRun queryRun;
// Add the table as a data source
qbds = query.addDataSource(tableNum(InventTable));
// Add a filter: ItemGroupId == 'RAW'
qbr = qbds.addRange(fieldNum(InventTable, ItemGroupId));
qbr.value('RAW');
// Execute and iterate
queryRun = new QueryRun(query);
while (queryRun.next())
{
InventTable inventTable = queryRun.get(tableNum(InventTable));
info(inventTable.ItemId);
}
Adding an inner join
Query query = new Query();
QueryBuildDataSource qbdsSales, qbdsCust;
qbdsSales = query.addDataSource(tableNum(SalesTable));
// Child data source joined to the parent
qbdsCust = qbdsSales.addDataSource(tableNum(CustTable));
qbdsCust.joinMode(JoinMode::InnerJoin);
qbdsCust.relations(false); // define relation manually below
qbdsCust.addLink(fieldNum(SalesTable, CustAccount),
fieldNum(CustTable, AccountNum));
Sorting and a date range
QueryBuildDataSource qbds = query.addDataSource(tableNum(SalesTable));
// Sort by CreatedDateTime descending
qbds.addSortField(fieldNum(SalesTable, CreatedDateTime), SortOrder::Descending);
// Range using SysQuery range value helper for a date interval
QueryBuildRange qbr = qbds.addRange(fieldNum(SalesTable, ShippingDateConfirmed));
qbr.value(SysQuery::range(mkDate(1,1,2026), mkDate(31,12,2026)));
Range types & expressions
QueryBuildRange qbr = qbds.addRange(fieldNum(SalesTable, SalesStatus));
// A "greater than or equal" style expression
qbr.value(strFmt('((%1 >= %2))',
fieldStr(SalesTable, SalesStatus),
any2int(SalesStatus::Backorder)));
// Force the range to be hidden/locked from the user if needed
qbr.status(RangeStatus::Locked);
Dynamic Query vs. while select
| Aspect | Dynamic Query | while select |
|---|---|---|
| Criteria known | At runtime (flexible) | At compile time (fixed) |
| User-editable filters | Yes (can bind to a dialog / form) | No |
| Reusability | High — share across UI, reports, batch | Low — embedded in code |
| Readability for simple cases | More verbose | Concise |
| Best for | Configurable, data-driven selection | Simple, fixed selections |
Points the interviewer wants to hear
Queryholds data sources;QueryBuildDataSourceis a table in the query.QueryBuildRange= a where filter;QueryRunexecutes and iterates.- Joins are added as child data sources with a
JoinModeandaddLink. - Use dynamic queries when criteria are runtime / user-driven or must be reusable.
- Use
fieldNum()/tableNum()intrinsics for compile-time safety.
Likely Follow-up Questions
- What is the difference between
JoinMode::InnerJoinandExistsJoin? - How do you filter a query with an expression range (e.g. greater-than)?
- How can you bind a dynamic query to a SysOperation dialog or a form data source?
- Why prefer
SysQuery::range()for building range values?
Key Takeaway
The Query framework — Query, QueryBuildDataSource, QueryBuildRange and
QueryRun — lets you build flexible, runtime-driven queries that plug into forms,
reports and batch jobs. Reach for dynamic queries whenever selection criteria aren't fixed at compile time.