Imagine you’re running a hybrid retail–manufacturing operation for consumer electronics. In the morning shift, planners want a live list of customer sales lines for “electronics” items that are still open (not fully shipped), limited to a specific warehouse, and sorted by the soonest delivery dates so the floor can pick and build first where it matters.
That’s where X++ comes in. X++ is the object-oriented language behind Dynamics AX / D365 Finance & Operations. It blends familiar imperative syntax with deep access to the application’s metadata (tables, relations, extended data types), so you can write business logic that feels close to the domain: queries, transactions, number sequences, and so on.
Inside X++ there’s a query framework that lets you build SQL-like queries in code. The star of that framework is QueryBuildDataSource
. Think of a QueryBuildDataSource
as one table in a query, along with everything about how you want to use it—filters, joins, sorting, and links to other data sources. You start with a root data source such as SalesLine
, then attach children like InventDim
and InventTable
to create a hierarchy that mirrors your join path. On each data source, you can add ranges (WHERE clauses), add sort fields, and choose whether to rely on dictionary relations or hand-wire links.
/// Example: Open electronics sales lines for Warehouse 51, morning shift, sorted by delivery date.
/// Target: Retail + manufacturing electronics, pick/build prioritization for 06:00–14:00 “today”.
static void WHS_RetailElectronics_OpenSalesLines(Args _args)
{
Query query = new Query();
QueryRun qr;
QueryBuildDataSource qbdsSL, qbdsDim, qbdsItem;
QueryBuildRange rStatus, rWhs, rGroup, rShift;
SalesLine sl;
InventDim dim;
InventTable it;
// --- Shift window: today 06:00–14:00 in UTC (adjust to your timezone rules as needed)
utcdatetime dtStart = DateTimeUtil::newDateTime(DateTimeUtil::date(DateTimeUtil::utcNow()), 060000);
utcdatetime dtEnd = DateTimeUtil::newDateTime(DateTimeUtil::date(DateTimeUtil::utcNow()), 140000);
// Root: SalesLine (open demand to pick/build)
qbdsSL = query.addDataSource(tableNum(SalesLine));
// Range: only open/backordered lines
rStatus = qbdsSL.addRange(fieldNum(SalesLine, SalesStatus));
rStatus.value(int2str(enum2int(SalesStatus::Backorder)));
// Range: delivery date inside the shift window
rShift = qbdsSL.addRange(fieldNum(SalesLine, DeliveryDate));
rShift.value(strFmt('(%1 >= %2) && (%1 < %3)',
fieldStr(SalesLine, DeliveryDate),
queryValue(DateTimeUtil::date(dtStart)),
queryValue(DateTimeUtil::date(dtEnd))));
// Sort by earliest delivery date
qbdsSL.addSortField(fieldNum(SalesLine, DeliveryDate), SortOrder::Ascending);
// Child #1: InventDim for warehouse filtering
qbdsDim = qbdsSL.addDataSource(tableNum(InventDim));
qbdsDim.relations(true);
qbdsDim.joinMode(JoinMode::InnerJoin);
rWhs = qbdsDim.addRange(fieldNum(InventDim, InventLocationId));
rWhs.value(queryValue("51")); // your warehouse code
// Child #2: InventTable for item group filtering
qbdsItem = qbdsSL.addDataSource(tableNum(InventTable));
qbdsItem.relations(true);
qbdsItem.joinMode(JoinMode::InnerJoin);
rGroup = qbdsItem.addRange(fieldNum(InventTable, ItemGroupId));
rGroup.value(queryValue("ELEC")); // your electronics item group
// Execute and stream results
qr = new QueryRun(query);
while (qr.next())
{
sl = qr.get(tableNum(SalesLine));
dim = qr.get(tableNum(InventDim));
it = qr.get(tableNum(InventTable));
info(strFmt("Pick candidate: SalesId=%1 Line=%2 Item=%3 [%4] Whs=%5 DelivDate=%6 QtyRemain=%7",
sl.SalesId,
sl.LineNum,
sl.ItemId,
it.ItemGroupId,
dim.InventLocationId,
sl.DeliveryDate,
sl.QtyRemaining));
}
// Optional: peek at the generated query text for debugging
// info(query.toString());
}
This approach fits a warehousing and retail manufacturing day because it focuses on real demand that still has to be fulfilled by targeting open or backordered sales lines. By linking to InventDim
, the query narrows down results to the warehouse that is actively picking and supplying the line, and by joining InventTable
, it limits the scope to the electronics group that planners are concerned with. The inclusion of a shift window makes the result practical, giving floor teams a manageable workload that aligns with the day’s operational timing rather than overwhelming them with future orders. Sorting by delivery date then means the soonest-due orders surface first, making the flow smoother for both planners and pickers.
A few practical considerations also come into play. If you move away from using relations(true)
and instead add explicit links with addLink(...)
, you must be very clear about which parent QueryBuildDataSource
you are referencing, as complex hierarchies can quickly become fragile. And if this query is meant to back a form, you have the option to place the ranges in executeQuery()
for a dynamic, runtime filter, or in init()
for a fixed default filter that applies as soon as the form loads.
Another point that often surfaces when working with queries in forms is the error message “Query missing QueryBuildDataSource for FormDataSource.” This typically shows up when the form expects its underlying query to include a QueryBuildDataSource
node for a given table but cannot find it. In practice, that means the form’s datasource is out of sync with the query it is bound to—either because the query doesn’t contain the table at all, or because a menu item passed in a query that lacks the expected structure. Without the right node, the runtime has no anchor for attaching ranges, joins, or filters, and the form will simply fail to load data as intended.
This alignment is important because much of a form’s runtime behavior depends on that query tree. If a developer changes a link type—for example, switching a delayed link to an inner join—without making sure the supporting query is structured correctly, the form ends up trying to extend a non-existent datasource. Likewise, code in init()
or executeQuery()
that attempts to add ranges or filters has nowhere to attach those restrictions. Ensuring that each FormDataSource
has a matching QueryBuildDataSource
in the underlying query is therefore essential: it maintains the mapping between the form’s UI elements and the actual data retrieval logic, and it helps make sure that the query manipulations you add in code are applied where you expect.
Have a Question ?
Fill out this short form, one of our Experts will contact you soon.
Call Us Today For Your Free Consultation
Call Now