Query Dataverse
There are three ways to say which rows a Dataverse step should read: fill in boxes, paste a query you already have and let it fill in the boxes, or run a FetchXML document exactly as written. This page is about choosing between them, and about what the second one will and will not take.
Which one you want
| Path | Use it when | What ends up on the step |
|---|---|---|
| Build query | You are starting from nothing, or you want to change what is there. | A table, a list of columns, filter rows, a sort and a page size. |
| Paste | You already have FetchXML, or a query address, and would rather not retype it. | The same as above. The pasted text is read here and never sent anywhere. |
| Run it as-is | Your FetchXML uses something the boxes cannot express — a total, a rolling date, a “rows where a related row exists” test. | The FetchXML document itself, run as written. |
All three live behind the same two buttons on a query step: Browse data, which picks the table and the columns, and Build query, which opens a window with a Build tab and a Paste tab. Everything below happens in that window. Nothing here costs anything — building and previewing are free, and only a published workflow's own runs are metered.
Build query: boxes, no syntax
Browse data lists the tables on the connection, and the columns on the table you pick, under both names — the friendly one you recognize and the logical one the query sends. Tick the columns you want.
Build query is the rest: filter rows, each one a column, an operator and a value, joined by a single and or or; a sort; and a page size. What will run is printed beside the form as you change it, so nothing is hidden.
Under Related rows you can also bring back rows from a related table alongside each row of the main one — the contacts on an account, say. The related columns arrive nested under the relationship rather than flattened into the parent row.
Two things it will not guess at, and both say so on screen: a value has to match the type you declared for it, and a filter it cannot draw — a bracketed group inside another group — is refused rather than flattened, because flattening one silently changes which rows you get.
Paste: a query you already have
The Paste tab takes a FetchXML document — the kind XrmToolBox writes — or an OData query: a whole address, a path, or just the query string. It tells you which of the two it thinks you pasted before you press anything.
Press Convert and it is read in your browser and turned into the same boxes the Build tab uses. This is an importer, not a second query language: the text you pasted is not sent to us, not stored on the step, and not seen by Dataverse. What runs afterwards is the same structured query as if you had filled the form in by hand.
What it reads
- The table, and the columns — named ones, or all of them.
- The sort order, and the page size.
-
Conditions using: equals, does not equal, greater than, greater or equal, less than,
less or equal, is empty, is not empty, is one of, contains, begins with, ends with, and
the three date comparisons (on, on or after, on or before). A
likebecomes contains, begins with, ends with or plain equals, depending on where the wildcard sits. - A join to a related table, where the connection's own list of relationships recognizes it. It becomes a Related rows entry, with the child columns you selected, and conditions inside a to-many join become that entry's own filter.
What it refuses, and why
Anything outside that list is named, with the line it is on, and the parts that did convert are still filled in for you. It never drops something quietly — a filter you can read on screen that is not the filter that runs is worse than no importer at all.
| Refused | Why, and what to do |
|---|---|
Totals and grouping — aggregate, groupby, alias |
The boxes count rows, they do not sum them. Run it as-is. |
Rolling dates — last-x-days and its family |
Dataverse works these out against “now” on its own side; there is no fixed value to put in a box. Run it as-is. |
“Mine” conditions — eq-userid and the rest |
They mean the signed-in Dataverse user, which is not a value the form can hold. Run it as-is. |
not-in, not-like, between, contain-values |
No box for them. between is usually two conditions — the refusal says which two. |
distinct="true" |
Not something the step can ask for. |
| A genuinely bracketed filter — (a or b) and c | The form draws one level of and/or. Flattening it would change which rows come back, so it is refused with its line. A nest that provably means the same thing as its parent is folded in silently, which is most of what XrmToolBox writes. |
A like with a wildcard in the middle, or a _ wildcard |
Contains, begins with and ends with are the three shapes there are. |
Paging inside the document — page, paging-cookie |
The step does its own paging. Set the page size instead. |
A second <entity>, a sort with an alias, entityname and valueof conditions |
One query reads one table. |
Joins that cannot become related rows: an “exists”-style join
(any, not any, exists, in),
a join inside a join, a many-to-many link table, the same relationship twice,
more than five of them, a sort inside a join, a join buried in a filter, or a
relationship this table does not have
|
The first kind narrows the parent rows, which related rows cannot do. The rest have no equivalent. The refusal quotes the relationship it could not place. Conditions inside a to-one join are refused too, and the message names the column on the parent row you can filter on instead. |
On a pasted OData query: $expand, $apply, $skiptoken, not, lambdas, paths through a relationship, substringof, and any other option |
Related rows are picked in Browse data; the rest have no box. A bare word that is not a number, true/false, an identifier or a date has to be quoted. |
The refusals are listed in the order they appear in your document, so the line numbers read downward.
Run it as-is: FetchXML, unchanged
When a paste is FetchXML and the importer could not take all of it, a second button appears: Run it as-is. It puts the document itself on the step and Dataverse runs it as written. This is how you get the things in the refusal list above — totals, grouping, rolling dates, “mine”, exists-style joins, real nested brackets.
The reason it is offered is shown beside it: the same refusal list, which is what made this the only way to run that document.
- It replaces the form, rather than sitting beside it. A document and a set of boxes cannot both run, so the step carries one or the other. Filling in a form that could never be saved is worse than not showing it.
- Edit as structured runs it back through the importer: what converts is filled in, the refusals are listed again, and your document is still in the box, so Run it as-is puts it back if you change your mind.
- Remove it, and Clear, drop it.
- The step's card on the canvas says FetchXML query, so a workflow reading a raw document never looks the same as one reading a built query.
What it will not accept
The document is checked before it goes anywhere, and refused with a sentence rather than quietly adjusted:
- It has to be well-formed XML starting with
<fetch>, with exactly one<entity>in it. - Up to 20,000 characters and 500 elements.
- No paging in the document itself — neither a page number nor a paging cookie. Paging is the step's own (see below), and a cookie copied out of somebody else's run returns a page nobody asked for.
- A page size inside the document is held to the same 1–500 ceiling as the box; over it is refused, never quietly reduced.
A document run as-is cannot also carry the boxes. Choosing columns, a filter, a sort or related rows beside it is refused, and the message names which — Dataverse will not combine the two, and quietly letting one win is how a workflow ends up reading rows nobody asked for.
How many rows come back
The page size is how many rows one run reads, not a limit on the answer. It starts at 5 and can go to 500. Ask for more than 500 and you get an error rather than a silently smaller number — a truncated answer that looks complete is the failure this rule exists to prevent.
A query that has more rows waiting says so, and hands back a marker a later call uses to carry on from where it stopped. That works the same way whichever path you took — boxes or a FetchXML document run as-is — even though Dataverse pages the two differently underneath. Give the query a sort order if you are going to walk it: continuing an unsorted read can repeat some rows and skip others.
There is one case where it says there is more and hands back no marker: a page Dataverse described in a way the step cannot reissue. It happens rarely and it is deliberate — reporting a partial read as a complete one would be the worse of the two wrong answers. If you see it, narrow the query.