Why Your Dataverse Model Is Producing Wrong Numbers, and How to Fix It in Production
A technical guide for the team whose reports disagree with finance: the modelling patterns that cause it, the fix order on a live system, and how to prove the numbers are right.
When finance reports disagree with Dynamics 365, month-end processing hangs and the portal times out, the data model is usually the cause. Four patterns produce it: uncontrolled many-to-many relationships that multiply rows in joins, self-referential hierarchies queried with the Under operators at depth, duplicate records inflating aggregates, and calculated or rollup columns recomputing at scale. Fix it in order. Stop the bleeding with a read facade or a denormalised column, quantify duplicates before merging anything, then remodel incrementally behind stable views. All of it runs on the live system without a freeze, but some things cannot be changed in place, including column data types, relationship types and table ownership type.
What does a Dataverse data model producing wrong numbers look like?
It rarely announces itself as a data model problem. It arrives as three complaints from three departments that nobody connects. Finance says the pipeline or revenue figure in the dashboard does not match their system and cannot explain the gap. Operations says month-end jobs, recalculations or exports run for hours and sometimes fail. Customers say the portal list of their orders or cases times out.
Each complaint gets a local fix: a manual adjustment in the board pack, a longer timeout, a smaller page size. The fixes hide the common cause, which is how the tables and relationships were modelled and how the reports and queries walk them. The table below maps the symptom to the pattern worth checking first.
| Symptom | Pattern to check first | Where to look |
|---|---|---|
| A total is higher than finance and the gap grows over time | Many-to-many joins fanning out, or duplicate accounts and opportunities | Report queries that join through an intersect table; duplicate detection job results |
| A total is lower than finance or lags by hours | Rollup columns not yet recalculated, or records excluded by status or security filters | Rollup column last calculated time; view filters and the security role of the report account |
| Month-end processing hangs or times out | Hierarchy queries with Under at depth, or processes touching many rows with calculated columns | Plug-in trace log, flow run history, and the FetchXML of the heavy views |
| The portal list times out for some customers only | Deep account hierarchies or many linked records behind the list filter | The list view query and the table permission relationship path for those customers |
| Different reports disagree with each other | Definitions implemented differently in each report | Whether reports share one model or each has its own calculation |
How do many-to-many relationships double count in reports?
A native many-to-many relationship in Dataverse stores each link as a row in a hidden intersect table. That is fine for saying "this opportunity involves these products" or "this account belongs to these segments". It becomes a reporting problem the moment a query joins through that intersect table and then sums a value that lives on the parent record. An opportunity linked to three segments appears as three rows, and its estimated value is counted three times.
Nobody writes that bug on purpose. It appears when a report author adds a segment filter or a segment column to an existing revenue report, in a FetchXML view with a link-entity, in Power Query when a related table is expanded, or in a Power BI model with a bidirectional relationship across a bridge table. The number looks plausible, so it survives until finance compares it with their own.
The second failure is uncontrolled growth: many-to-many relationships added for every new grouping idea, several of them between the same two tables, with no owner deciding which one is authoritative. When the link needs data of its own, such as quantity, role or a primary flag, a custom junction table is the better model; our guide to linking multiple products to a single case sets out that choice in detail.
- Check every report that sums a parent value while filtering or grouping by something reached through an intersect table.
- In Power BI, keep the bridge table out of the path of parent measures, or allocate the value explicitly, for example to a primary segment only.
- List all many-to-many relationships per table and name an owner and a purpose for each; retire the ones nobody can explain once nothing reads them.
Why do hierarchies queried with Under slow everything down?
Dataverse lets a table have a self-referential relationship marked as hierarchical, such as parent account or parent case, and FetchXML offers hierarchy operators such as under, eq-or-under, above and eq-or-above to query it. They are convenient: "all opportunities for accounts under this group" becomes one condition. They are also expensive when the hierarchy is deep or wide, because the platform has to resolve the whole tree before it can apply the rest of the query.
The cost multiplies when the operator sits in a view used by many people, a portal list filter, a plug-in running on every update, or a month-end process that runs the query once per top-level account. Circular or accidental parents, such as an account set as the parent of its own ancestor through an import, make the tree larger than anyone thinks. Microsoft documents limits and behaviour for hierarchical queries that change over time, so check the current documentation rather than relying on a remembered number.
- Find where the operators are used: saved views, portal lists, FetchXML in plug-ins, flows and reports.
- Measure the actual depth and breadth of the tree and look for loops and orphaned branches before tuning anything.
- Where reports only need the top of the tree, store it: a "group account" lookup maintained on each record turns a hierarchy walk into a simple filter.
When do calculated, rollup and formula columns become the problem?
They behave differently, and most wrong numbers come from treating them as the same thing. Calculated columns and formula columns are evaluated when the row is retrieved, so the value is current but every view, export and report that selects them pays the calculation cost on read, including across thousands of rows. Rollup columns are stored values recalculated by asynchronous system jobs on a schedule, or on demand, so they are cheap to read but can be stale, and a mass recalculation competes with everything else in the environment.
At small volume none of this is visible. At scale it produces exactly the month-end symptoms: a report run straight after a large import shows totals that the next scheduled rollup job will change, a process that reads calculated columns across a large table slows down, and chains of calculated columns that reference other calculated columns multiply the work. Check the current Microsoft documentation for the limits and recalculation behaviour in your environment, because both are revised.
| Column type | When the value is computed | Typical failure at scale | Remediation |
|---|---|---|---|
| Calculated | When the row is retrieved | Slow views and exports; chains of dependent calculations | Remove from high-volume views and exports, or store the value when it changes |
| Formula (Power Fx) | When the row is retrieved | Same as calculated, plus logic spread across many columns that is hard to audit | Keep formulas simple and move shared business definitions into one place |
| Rollup | Asynchronously by system jobs, on a schedule or on demand | Totals stale after bulk changes; heavy recalculation jobs | Show the last calculated time, trigger recalculation after bulk loads, or maintain the total in a plug-in |
What should you fix first to stop the bleeding?
Give the business one set of numbers it can rely on before touching the model. Changing relationships while finance is still arguing about the board pack adds a moving target to a dispute. A read facade is a layer that consumers read from, which presents correct figures while the tables underneath are still wrong, and which later lets you change those tables without every report breaking.
Choose the lightest facade that answers the disputed questions. Whichever you choose, write down the definition it implements, because that definition is what finance will sign off later.
| Facade | How it works | When it fits |
|---|---|---|
| A reporting view on an analytical copy | A SQL view over data exported with Azure Synapse Link or Link to Microsoft Fabric that de-duplicates and allocates many-to-many values correctly. | Several reports need the same corrected figures and an analytical copy exists or is planned, as described in our reporting limits guide. |
| A Power BI model with de-duplication | One semantic model that maps duplicate customers to a survivor ID and keeps bridge tables out of the path of parent measures. | Reporting already runs through Power BI and the dispute is about a handful of measures. |
| A denormalised column | A stored column, such as group account or a counted-in-revenue flag, maintained by a plug-in or a flow when the source changes. | Views, portal lists or processes need a fast, simple filter instead of a hierarchy walk or a join. |
How do you measure what duplicates are doing to each report?
Quantify before you merge anything. Merging in Dynamics 365 moves child records onto the survivor and there is no unmerge, so a merge done to fix one report changes history in every other report too. The work that comes first is measurement: run duplicate detection jobs with rules tuned to real match keys, such as registration number, normalised email or website domain, and export the candidate groups.
Then count the impact per report rather than per table. For each disputed figure, how much of the value sits on records that belong to a duplicate group, and would merging move it between regions, owners or periods? That turns "we have duplicates" into a list of which merges change which board numbers, which is what finance and the data owners need to approve. The matching rules, reversible merge batches and alternate keys that follow are covered step by step in our duplicate data cleanup guide; this page does not repeat them.
How do you remodel a live Dataverse system without a change freeze?
Change alongside, never in place. The pattern is sometimes called expand and contract: add the new structure next to the old one, fill it, move consumers across one at a time, and remove the old structure only when nothing reads it. Every step is small, deployable through your normal managed solution pipeline, and reversible until the last one. Users keep working throughout.
| Step | What happens | What protects the live system |
|---|---|---|
| 1. Add the new structure | Create the new table, junction table, lookup or column in a solution, alongside the old one. | Nothing reads it yet, so deploying it changes no behaviour. |
| 2. Write to both | Plug-ins or flows keep the new structure in step with the old one for every new change. | The old model stays authoritative; the new one is checked against it. |
| 3. Backfill history | Load existing records into the new structure in batches outside peak hours. For throughput and throttling, follow the bulk import strategy. | Batches are logged and restartable, and control totals are compared after each one. |
| 4. Switch consumers | Point the facade, then views, forms, portal lists, integrations and reports at the new structure, one consumer per release. | Each switch can be reverted on its own if a total moves unexpectedly. |
| 5. Stop writing to the old structure | Remove the dual write once every consumer has moved. | Only done after a full period reconciles on the new model. |
| 6. Retire | Remove the old columns, relationships or tables from forms and views, then delete them. | Dependency checks and an integration inventory confirm nothing still uses them. |
What cannot safely be changed in place in Dataverse?
Some decisions are fixed when a table, column or relationship is created, and others are technically possible but break things you cannot see from the maker portal. These are the changes that need the alongside pattern above rather than an edit. Where the platform behaviour is revised, confirm it against current Microsoft documentation before planning around it.
| Change | Why it cannot be done in place | Safe route |
|---|---|---|
| Column data type, such as text to lookup or whole number to currency | The data type of an existing column cannot be changed; only some format options within a type can. | New column, backfill, switch consumers, retire the old column. |
| Relationship type, such as one-to-many to many-to-many | A relationship cannot be converted to another type. | New relationship or junction table alongside, backfill the links, switch, retire. |
| Table ownership type (user or team owned versus organization owned) | Fixed when the table is created. | New table with the right ownership, migrate rows and related records. |
| Schema names and the publisher prefix | Schema and logical names, including the prefix, cannot be renamed after creation; only display names can. | Live with the name, or create new components under the right publisher and migrate. |
| Deleting a column or relationship used elsewhere | Solution dependencies block some deletions, but integrations, Power BI models, exports and external code that reference it by name break silently. | Inventory every consumer, remove from forms and views first, monitor, then delete. |
| Cascade behaviour on a busy relationship | Can be changed, but alters what happens to child records on assign, share, reparent and delete across existing data. | Test on a copy of production data and schedule it with the owners of the child tables. |
How do you prove the numbers are right to finance?
Agree control totals per period with finance before the first change, then run them after every change. A control total is a figure both sides can produce independently: invoiced revenue per month, count of active contracts at period end, value of orders per region. Where revenue is recognised in a finance system, that system owns the number and the CRM model reconciles to it. Solzet integrates Dynamics 365 with finance systems rather than implementing them, so this reconciliation is always done with your finance team.
- Write each control total down with its definition, filters, currency, time zone and owner, and record the agreed figure for several closed periods.
- Take the same totals from the facade before any remodelling, and explain every difference in writing before changing anything.
- Re-run the totals after each backfill batch, each consumer switch and each merge batch, and keep the results as evidence.
- Do not retire the old structure until at least one full close has reconciled on the new model.
- Give disputed figures an "as at" time on every report, so timing differences are not mistaken for model errors.
Is a rebuild or a different platform the better answer?
Usually not. A model producing wrong numbers is fixable in production, and a remediation that keeps the system running is cheaper and less risky than a rebuild that has to reproduce years of data, integrations and user habits before it delivers anything. If the model sits alongside slow forms, failing integrations and nobody who understands the solution, start with an independent Dynamics 365 health check and technical audit so the remediation is scoped against the whole environment.
We recommend the right solution - whether that's Microsoft Dynamics 365, Power Platform, or a custom-built CRM. Some businesses need the Microsoft ecosystem. Others need full control without licensing. We deliver both. Where the audit shows that Microsoft licensing or the platform boundary is the underlying problem rather than the model, a custom CRM on React, Node.js, PostgreSQL or .NET gives you direct control of the database schema and its reporting replicas.
Should a data model this tangled be remediated on Dataverse or rebuilt on a platform you own?
Can afford licensing and want the Microsoft ecosystem
Dynamics 365
Microsoft 365, Teams and Outlook integration, a mature partner ecosystem, Copilot, and apps for sales, service and field operations that are configured rather than built.
Need full control and zero licensing
Custom CRM
A CRM built on React, Node.js, PostgreSQL or .NET that you own outright: your data model, your hosting, no per-user subscription, and features shaped exactly to your process.
Not sure which fits
We help you decide
A short discovery weighs licensing budget, process complexity, integrations and long-term ownership, then recommends one path. We deliver both, so the recommendation has no reason to lean.
How does Solzet help remediate a Dataverse data model?
We work in the order of this page. First the diagnosis: which reports, processes and portal pages are affected, which of the four patterns cause it, and the agreed control totals. Then the read facade, so the business has numbers it can use this month. Then the duplicate impact per report, and the incremental remodel behind that facade, released through your pipeline one consumer at a time with the reconciliation evidence kept for finance.
Solzet delivers remotely from Yerevan, Armenia, with senior consultants and full-stack developers and 8+ years of Dynamics 365 Customer Engagement and Power Platform work, directly or white-label for Microsoft partners.
What do people ask us?
Why do Dynamics 365 reports show different numbers from our finance system?
Usually because of the data model or the report definitions rather than a platform fault. Joins through many-to-many relationships count a parent value once per link, duplicate accounts split or double revenue, rollup columns are stale until their scheduled recalculation runs, and reports apply different status, currency or date filters. Agree control totals per period with finance, compare them with the CRM figures, and trace each difference to one of those causes before changing anything.
How do many-to-many relationships cause double counting in Dataverse reports?
Each link in a native many-to-many relationship is a row in an intersect table. When a report joins through that table and sums a value stored on the parent, such as opportunity estimated value, the parent appears once per link and its value is counted several times. Keep bridge tables out of the path of parent measures, allocate values explicitly, or use a junction table with a primary flag when the link needs data of its own.
Are rollup columns in Dataverse calculated in real time?
No. Rollup columns are stored values recalculated asynchronously by system jobs on a schedule, and they can also be recalculated on demand, so they can be stale after bulk changes. Calculated and formula columns are different: they are evaluated when the row is retrieved, which keeps them current but adds cost to every view and export that selects them. Check current Microsoft documentation for recalculation behaviour in your environment.
Can we change a column data type in Dataverse without losing data?
Not in place. The data type of an existing Dataverse column cannot be changed, only some format options within the same type. The safe route is to create a new column with the right type, backfill it from the old one, switch forms, views, integrations and reports across one at a time, reconcile, and then retire the old column. The same alongside approach applies to relationship types and table ownership type.
Do we need a change freeze to fix a Dataverse data model?
No. Remodel alongside the existing structure: add the new table or relationship, keep it in step with plug-ins or flows, backfill history in batches outside peak hours, switch consumers one release at a time, and retire the old structure only after a full period reconciles. Each step is small and reversible, so users keep working throughout and finance can check control totals after every change.
Should we merge duplicate records before fixing the reports?
Not first. Merging moves child records to the surviving record and there is no unmerge, so it changes history in every report at once. Put a read facade in place that maps duplicates to a survivor for reporting, run duplicate detection jobs to quantify the groups, measure how much of each disputed figure they affect, and only then merge in approved, logged batches with control totals checked after each one.
Why is our Power Pages portal timing out on lists of records?
Often because the list query is expensive rather than because the portal is slow. Lists filtered through deep account hierarchies, table permissions that follow long relationship paths, many linked records behind a filter, or calculated columns in the view all add work to every page load. Measure the query, then simplify it with a stored lookup such as a group account column or a narrower view before tuning the portal itself.
Where should you go next?
Dynamics 365 health check and technical audit
An independent review of data integrity, performance, configuration and security with a prioritised remediation roadmap.
Fixing duplicate customer data in Dynamics 365
Matching rules, reversible merge batches, alternate keys and intake controls that keep data clean.
Dynamics 365 reporting limits and Power BI refresh
The aggregate record limit, the fix ladder and the analytical copy for board reporting.
Fast Dataverse bulk import for millions of rows
Throughput, bulk messages and throttling for backfilling a new structure.
Linking multiple products to a single case
When a many-to-many relationship is enough and when a junction table is the right model.
Custom CRM Development
CRM on React, Node.js, PostgreSQL and .NET for organizations that need full control without Microsoft licensing.
Which solution is right for your business?
Tell us what you need. A senior consultant replies within one business day with a recommendation - Dynamics 365, Power Platform, or a custom-built CRM - not a sales script.