Almanac
Microsoft/dataversePower Platform

Consultant KB for Microsoft Dataverse, the data layer under Dynamics 365 and the Power Platform: data model, security model, business logic, APIs and integration, search and queries, analytics and Fabric, ALM and solutions, administration, governance and compliance, and Dataverse as an agent data platform, plus cross-cutting decision guides. Implementation notes, configuration decisions and the gotchas that bite on real projects. Populated by the daily author agent from the Dataverse release plans, docs repo and product blog, plus the author's own consultant notes.

feature-odata-query-patterns.mdv1 · history
CurrentApplies to MakerUpdated last monthSource Microsoft Learn

What it does

The Dataverse Web API is an OData v4.0 service, so every read is a GET against an entity set shaped by $select, $filter, $expand, $orderby, $top, $count and $apply. It's the default way anything outside the platform reads Dataverse data.

Key facts

  • Supported query options are $select, $expand, $orderby, $filter, $apply, $top and $count. Option names are case-sensitive.
  • $skip, $search and $format aren't supported. That trips people coming from other OData services.
  • Without $top, a request returns up to 5,000 standard table rows and 500 elastic table rows. Use paging instead of $top when you want more, and don't combine them.
  • GET URLs are limited to 32 KB. Move the query into a $batch POST and you get double that, 64 KB.
  • Entity set names are usually the plural table name but not always. Read the service document rather than guessing.
  • Formatted values only appear if you send Prefer: odata.include-annotations="OData.Community.Display.V1.FormattedValue". Without it you get raw integers for choices and state codes.
  • $orderby on a choice column sorts by the stored integer, not the localised label. FetchXML does the opposite. This catches people out constantly.
  • Parameter aliases work in $filter and $orderby but not inside $expand.
  • OData is still on 4.0. The 4.01 enhancements aren't available in the Dataverse Web API.

When to use / skip

This is the default. Use it for client apps, integrations, flows, anything that isn't a saved view. Reach past it to FetchXML only when you hit one of the documented gaps: joining tables that have no relationship, nested $expand across N:N, cross-table column comparison, countcolumn with distinct, or the late materialise hint. If you find yourself writing an OData query with $expand three levels deep and a dozen selected columns, stop — you're building something the platform will throttle, and you probably want a different shape entirely.

Configuration decisions

  • Which columns you actually need in $select. Leaving it off is the single most common cause of slow reads, and logical columns are worse than ordinary ones because Dataverse has to stitch them from other tables.
  • Whether related data comes back via $expand in one round trip or as separate requests, traded against payload size and URL length.
  • Paging strategy, and whether the caller can cope with cookie-based paging or needs a bounded $top.
  • Whether to batch. Batching moves query options out of the URL, groups operations, and gives you per-operation error information.
  • Whether to ask for formatted values, which makes payloads noticeably bigger.

Gotchas

  • startswith(column,'%234') looks like a legitimate filter but the leading wildcard character makes it a table scan. Dataverse throttles these aggressively and returns a dedicated DataEngineLeadingWildcardQueryThrottling error.
  • Filtering on formula or calculated columns forces Dataverse to compute the value for every candidate row. Same story — dedicated error, aggressive throttling.
  • Conditions on memo columns, or string columns with a MaxLength over 850, can't use an index. Those go to Dataverse search, not to a filter.
  • The choice sort behaviour differing between OData and FetchXML means a view and an API call over the same data can legitimately return different orderings.
  • Nested $expand on N:N relationships isn't supported and the error message doesn't make that obvious.

Consultant notes

  • Get the integration team to log the query strings they're sending. Nine times in ten the performance conversation ends the moment you see $select is missing.
  • Service protection limits are normal, not exceptional. Any integration that doesn't implement retry-after handling will fail eventually, so treat it as a design requirement rather than a defect.
  • When a client wants free-text search over a description field, the answer is Dataverse search. Don't let it become an OData contains filter that works fine on 10,000 rows and dies on a million.
  • Dataverse REST Builder and FetchXML Builder both generate OData for you. Neither is supported by Microsoft, which matters if the output ends up embedded in something operational.

Worth revisiting if the Web API moves to OData 4.01, or if $search ever becomes supported.

Was this accurate?