We are in official beta.

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

PathUse it whenWhat 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 like becomes 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.
One thing a join changes, and it says so. In FetchXML a join drops parent rows that have no match; here, related rows are attached to every parent row and none are dropped. That is more rows than the original query returned, not fewer, so the conversion tells you which relationship it did that to rather than changing your answer quietly.

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.

RefusedWhy, 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.