TL;DR
Because a demo has one entity, few rows and co-located environments, and production has none of those. Across the virtual entities overview, the virtual entities FAQ and the entity-modeling page, the only virtual entity overhead figure Microsoft publishes is for the co-located case; there is an FAQ answer headed "The virtual entity performance is slow when a virtual entity has relationships to other entities"; and Microsoft states that the exemption applies only to the Finance and Operations endpoints the virtual entity plugin invokes, and that because the request is made through the Dataverse API, Dataverse service protection limits might still apply to it.
Why are virtual tables fast in the demo and slow in production?
Because a demo is one entity, a handful of rows, and two co-located environments — and production is none of those. Virtual tables [GA] hold no local copy of the data. Every query is a live call into the ERP at the moment it is asked, so everything that grows between demo and production grows the call.
Three documented mechanisms explain the gap, and a fourth failure in this family is not a performance problem at all. One of the three carries an FAQ heading that says the quiet part outright, and none of them is visible in a short demo.
Mechanism one: there is no local copy, so distance is priced into every query
The Virtual entities overview (page dated 2026-01-21) states the design: "By definition, the data for virtual entities doesn't reside in Dataverse. Instead, it continues to reside in the app where it belongs."
A query is not a lookup against a replica — it "causes a Secure Sockets Layer (SSL)/Transport Layer Security (TLS) 1.2 secure web call to the CDSVirtualEntityService web API endpoint of finance and operations apps." Then the deployment note:
"For optimal latency in virtual entity calls, always use finance and operations apps and Dataverse that are co-located in the same Azure region. When finance and operations apps and Dataverse are co-located, the virtual entity overhead is expected to be less than 30 milliseconds (ms) per call."
Read what is and is not published there. Microsoft gives a figure for the co-located case and an instruction to always co-locate. It publishes no figure for environments in different regions — not on that page, not on the virtual entities FAQ, not on the entity-modeling page, the three Microsoft pages on this feature opened here. The only overhead number Microsoft stands behind is conditional on a choice made when the environments were created. Ours is the inference that follows: region is a property of an environment, so correcting it is an environment move, not a tuning exercise.
Mechanism two: relationships, and Microsoft's own heading calls it slow
The finance and operations virtual entities FAQ carries a question headed "The virtual entity performance is slow when a virtual entity has relationships to other entities. Is there guidance on how to avoid these problems?" The answer names the mechanism and hedges its own completeness:
"Performance can be slow for several reasons when a virtual entity has relationships to other entities. This section will be updated as new patterns are identified. The following guidance reflects current knowledge. When virtual entities have relationships to other entities, the virtual entity framework needs to query the related entities if the field select list includes the foreign key values for the related entities. By default, queries against the entities return all fields unless the caller requests a specific set of fields. Specify a narrow select list to help prevent slow performance."
There is the demo-to-production gap in two sentences. The default is to return every field, and every foreign key in that list triggers a query against the related entity. A demo entity with no relationships returns in one call; the same entity in a configured production model returns in one call plus a query against each related entity whose foreign key sits in the select list.
Entity modeling explains why the count grows quietly: generating the virtual table adds a lookup field per relation, so "you create the same number of lookup fields (one per related entity) in the source virtual entity."
Microsoft's worked treatment sits on a Human Resources page, Optimize Dataverse virtual table queries, stamped "Applies to these Dynamics 365 apps: Human Resources" and to be read with that scope. It names four symptoms: slow query execution, query timeout, an unexpected error, and throttling.
Mechanism three: the exemption trap — you moved the meter, you did not remove it
This is the most misunderstood thing in the subject, and Microsoft resolves it in one paragraph.
Virtual tables are on the exemption list on Service protection API limits (page dated 2026-01-20), introduced with a hedge — "The following services are currently exempt from them" — as "Power Platform virtual tables for finance and operations apps". Then the paragraph that decides the design:
"The exemption for virtual tables applies only when the Microsoft Power Platform integration with Finance and Operations apps is enabled. The service protection API limits apply to virtual tables if the integration isn't enabled for the Finance and Operations apps environment. When the integration is enabled, the exemption applies only to the Finance and Operations apps API endpoints that the virtual entity plugin invokes. The Finance and Operations apps service doesn't throttle the request. However, because the request is made through the Microsoft Dataverse API, Service protection API limits might still apply to the request."
Both halves, in Microsoft's words. The exemption is conditional on the integration being enabled, covers only the finance and operations endpoints the plugin calls, and leaves the Dataverse meter running. Keep Microsoft's "might still apply": the page does not promise the Dataverse limits will bite, only that the exemption does not remove them.
That these are genuinely different meters is provable from the error strings. The Dataverse service protection API limits page (dated 2026-01-09) publishes error code -2147015902, hex 0x80072322, message Number of requests exceeded the limit of 6000 over time window of 300 seconds.
The finance and operations page publishes "Number of requests exceeded the limit of 6000 over the time window of 300 seconds." Same reading, different meter, different string — down to one word.
A second trap is folded inside. On the finance and operations side, user-based limits were disabled by default in version 10.0.35 and, in 10.0.36, "the limits are disabled on all environments, and the option to enable the limits is removed." An architect who knows that can conclude the request limit is behind them. The Dataverse limits page describes its own as live and evaluated per user — and that is the API the request is made through, which is why Microsoft says its limits might still apply.
The failure that is not a performance failure at all
One symptom in this family is an identity problem wearing a latency costume, and Microsoft's claim about it is narrower than the version that circulates. From Authorization on the overview:
"The Dataverse SYSTEM user doesn't have access to all entities in finance and operations, nor do the out of the box service-to-service applications. Failure to use a proper user or app-user context might result in errors with messages like Unable to determine the user in CDS."The SYSTEM user lacks access to all entities — not access altogether — and a wrong context "might result in errors with messages like" that string. Both hedges are Microsoft's and both earn their place, because the second is why this gets misdiagnosed: one misconfiguration surfaces differently depending on the entity. The fix is to associate the organization context with an application or end user that has permissions to the virtual entity in finance and operations.
What a demo cannot show you
- One entity, no relationships — What production has: Several relations, each adding a lookup field · Documented consequence: The framework queries the related entities when foreign keys are in the select list
- A narrow result by accident — What production has: The same default over a wide entity · Documented consequence: Queries "return all fields unless the caller requests a specific set of fields"
- Co-located environments — What production has: Whatever the tenant was built with · Documented consequence: Microsoft's overhead figure is stated for the co-located case only
- One tester — What production has: Concurrent users, flows and service-principal integrations, all metered on the Dataverse side · Documented consequence: Dataverse limits are evaluated per user and each authenticated user has an independent limit — and the exemption does not reach them
Where we would draw the line
We use virtual tables for the read they are good at, and we do not build an Optimizer's bulk read on them. A live projection with no copy is right for reading a record in context, and it is the cleanest expression of not touching the ERP core: nothing is replicated, nothing is installed on the ERP, and the query runs as a looked-up finance and operations user inside a named Security Role.
It is the wrong instrument for pulling a season of demand history to optimize a safety-stock level. That read is wide and repeated, and each mechanism above scales against it. For that we read through a surface built for volume, compute on Azure in your tenant, then write the optimized value back through a narrow approved path — surrounding the ERP rather than sitting inside it.
What to check this week
- Confirm whether your Dataverse and Finance and Operations environments share an Azure region. Across the virtual entities overview, the virtual entities FAQ and the entity-modeling page, the only overhead figure Microsoft publishes is conditional on co-location; region is set when the environment is created.
- Open any Power App or report on a virtual table and look at its select list. If nobody narrowed it, the default returns all fields and pulls the foreign keys with them.
- Count the lookup fields on your busiest virtual table. Each relation generated one, and each is a potential extra query against a related entity.
- Ask which meter your last throttling incident was on. The finance and operations exemption is conditional and does not reach the Dataverse API, and the two publish different strings for it.
About Cognilium Cognilium is the AI optimization layer for Dynamics 365 — complementary apps that optimize the pricing, inventory, warehouse and planning decisions your ERP manages but can't optimize. Built on Power Platform, Dataverse and Azure. https://cognilium.ai · https://www.linkedin.com/company/37180269/
If a virtual table read is slower than the demo suggested and you want the mechanism-by-mechanism version against your own environment, book a fifteen-minute call and we will walk it with you live — no deck. https://cognilium.ai
Sources
- Virtual entities overview (Finance & Operations) — page dated 2026-01-21
- Finance and operations virtual entities FAQ — page dated 2022-08-03, last updated 2026-03-18
- Entity modeling (virtual tables) — page dated 2026-01-21
- Limitations and troubleshooting virtual tables with Power Apps — page dated 2026-05-15
- Service protection API limits (Finance & Operations) — page dated 2026-01-20
- Service protection API limits (Microsoft Dataverse) — page dated 2026-01-09
- Optimize Dataverse virtual table queries — Human Resources; page dated 2021-04-02, last updated 2025-07-16
Sources
- learn.microsoft.com — virtual entities overview
- learn.microsoft.com — limits tshoot virtual tables
- learn.microsoft.com — service protection api limits
- learn.microsoft.com — api limits
- learn.microsoft.com — faq
- learn.microsoft.com — entity modeling
- learn.microsoft.com — hr developer optimize virtual table queries
Share this article
Muhammad Mudassir
Founder & CEO, Cognilium AI
Muhammad Mudassir
Founder & CEO, Cognilium AI
Mudassir Marwat's argument is that ERP systems record decisions they never optimise.
