See your company like never before
Power BI deep-dives, migration playbooks, and data strategy for enterprise teams.


Join dozens of organizations who have moved to Beyond The Analytics. Book your personalized demo today
Pick the right Dataverse connection method, build a star schema over CRM entities, and ship pipeline and service dashboards on Dynamics 365 data.
The built-in Dynamics 365 dashboards stop being useful around the same point your CRM stops being a contact database. Once Sales, Customer Service, and Field Service teams share one Dataverse environment, the questions get cross-functional: pipeline coverage by service region, case backlog versus active opportunities, forecast accuracy by sales manager. None of that lives in a single D365 view. It lives in Power BI, sitting on top of the same Dataverse tables that power the CRM itself. This guide covers the three connection methods Microsoft supports, how to turn Dataverse's normalised entity model into a Power BI star schema, the dashboard patterns sales and service leaders actually use, and the operational stuff most tutorials skip: refresh strategy, option sets, polymorphic lookups.
Quick answer: Built-in Dynamics 365 dashboards are scoped to a single module and a single user's view of records. Power BI is the layer where cross-module analytics, executive rollups, and forecasting live, and where you bring in non-CRM data like ERP financials or marketing spend.
Out-of-the-box D365 dashboards are tied to one app (Sales Hub, Customer Service Hub) and rendered against live Dataverse queries with row-level security applied. That works fine for a sales rep checking their own pipeline. It does not work for a CFO who wants ARR forecast by business unit, a service director who wants SLA breach trends by product line, or a sales manager comparing this quarter's win rate to last year's.
Three structural limits show up fast. Built-in dashboards are single-module: a Sales Hub dashboard cannot easily mix Opportunity data with Case data from Customer Service. They have no historical snapshots: Dataverse stores the current state of every record, so to see how your pipeline looked last month you need a separate analytics layer. And they have limited modelling: D365 charts don't give you DAX, so a measure like "Weighted pipeline excluding stalled opportunities older than 90 days" isn't possible inside a Sales Hub dashboard.
Power BI fixes all three. The trade-off is that you now own a semantic model, and the rest of this post is about getting that model right.
Quick answer: Use the native Dataverse connector in Import mode for small CRM environments: under roughly 500,000 rows per fact table, refresh frequency of 30 minutes or less, no need for historical snapshots. It is the simplest method and the only one that works without admin-level Azure setup.
The native Power Query Dataverse connector (Microsoft Learn) talks to Dataverse over the Web API. In Import mode, Power BI pulls rows into the VertiPaq in-memory engine on each refresh. It's the right starting point when the CRM is small to mid-size (under 500,000 rows in your largest fact table), daily or hourly refresh is enough, you don't have permission to provision Azure resources, or you want to ship a first dashboard quickly.
The limits show up around volume and freshness. Each scheduled refresh pulls full or incremental partitions over the Web API, which both consumes Dataverse API capacity and slows the experience for Dynamics users while the refresh runs. Microsoft's own modelling guidance for Power Platform flags this: for anything above small-data scenarios, plan to move to Synapse Link or Fabric Link.
DirectQuery against the Dataverse connector exists, but Microsoft is explicit about when to use it: query result under 20,000 rows after filters, row-level security passed through to Dataverse, or near-real-time non-negotiable. Each query has a 10-minute timeout. For most CRM analytics workloads, this is the wrong default.
In Power BI Desktop, choose Get Data → Power Platform → Dataverse and enter your environment URL (e.g. org12345.crm.dynamics.com). Select the entities you want (Account, Contact, Opportunity, OpportunityProduct, Lead, Case) and load. Two practical tips: pick the named entity tables, not the system-generated views like saved_query that look like tables but aren't. And filter columns early. Dataverse entities can have 50 to 150 columns by default, most of which are housekeeping fields that don't belong in the model.
Quick answer: Use the Dataverse TDS endpoint when you need to write actual SQL against CRM data (joins across many entities, complex filtering, pre-aggregation) and the result set is moderate. It is read-only, has a five-minute query timeout, and is best for ad-hoc analysis and Paginated Reports, not high-volume Power BI refreshes.
The TDS (Tabular Data Stream) endpoint (Microsoft Learn) is a SQL Server-compatible read-only endpoint sitting in front of Dataverse. Connect from SQL Server Management Studio, Power BI's SQL Server connector, or Azure Data Studio at org12345.crm.dynamics.com,5558 using an Azure AD account.
The streaming model changed recently. The old TDS endpoint had a hard 84MB result size limit; the modern endpoint uses streaming and effectively has no row limit, but it does enforce a fixed five-minute query timeout (two minutes if your query contains SELECT *, deep joins, or other patterns the optimiser flags as heavy). That makes it suitable for surgical queries, not for nightly bulk extracts.
Where the TDS endpoint earns its place: Paginated Reports (the SSRS successor in Power BI) work much better against TDS than the Web API connector, so operational reports, statements, and invoices belong here. Ad-hoc analysis in SSMS when you're debugging data quality issues is the other strong fit. DirectQuery for narrow real-time reports against moderate tables also works.
What it is not is a substitute for a properly modelled semantic layer on a Synapse Link or Fabric Link copy of the data. Hammering the TDS endpoint from a scheduled Power BI refresh hitting twenty tables is a recipe for timeouts and angry CRM users.
Enable the TDS endpoint at the environment level in the Power Platform admin centre (Environment Settings → Product → Features → Enable TDS endpoint). The user connecting needs a Dataverse security role with read permission on the tables they query.
Quick answer: Azure Synapse Link for Dataverse (and its successor, Link to Microsoft Fabric) continuously exports Dataverse changes to Delta Lake files in Azure Data Lake or OneLake. It has zero impact on Dataverse operational performance, supports near-real-time freshness, and is the recommended pattern for any enterprise Dynamics 365 analytics workload.
For most new D365 analytics builds, this is the architecture to plan toward. Both options use change tracking, not API polling, to write Dataverse changes to a Delta Lake-formatted store. Power BI reads via DirectLake on Fabric or Synapse Serverless SQL pools on Synapse Link. The Dataverse environment never sees the analytics queries.
Two flavours. Azure Synapse Link for Dataverse writes to ADLS Gen2 and is exposed via Synapse Serverless SQL, which suits organisations already on Synapse Analytics or doing complex transformations in Synapse pipelines. Link to Microsoft Fabric writes to OneLake as a zero-copy shortcut, and is the better fit for organisations on Microsoft Fabric, DirectLake semantic models, and unified Purview governance. Microsoft's guidance is to use Link to Microsoft Fabric for new builds where Fabric capacity is available. Synapse Link remains supported and is fine for existing Synapse-based architectures.
Changes land in the lake in waves, typically within a few minutes. Microsoft describes this as "near-real-time," which in practice means new records are queryable within roughly 15 minutes under normal load. Plenty for forecast accuracy, pipeline movement, and service KPIs; not enough for sub-second operational views like a sales rep refreshing their own pipeline list. That's still the job of the D365 app itself.
This is not a no-cost option. Synapse Link requires an Azure subscription with storage (ADLS Gen2) and optional compute (Synapse Serverless SQL pool, charged per TB scanned). Fabric Link requires Fabric capacity (F-SKU) in the tenant. You also need Dataverse System Administrator to configure the link, plus either Azure Owner on the target subscription (Synapse Link) or Fabric Workspace Admin on the target workspace (Fabric Link).
For most BTA clients in the UAE and Saudi Arabia, the right pattern is Fabric Link in a workspace deployed to UAE North or a Saudi central region. That keeps Dataverse, OneLake, and Power BI in the same regional boundary, which matters for data residency under PDPL and NDMO requirements.
Quick answer: Under 500K rows and short-term needs go to the native Dataverse connector. Ad-hoc SQL and Paginated Reports go to the TDS endpoint. Anything enterprise, multi-million-row, or production-grade goes to Synapse Link or Fabric Link.
| Scenario | Recommended method |
|---|---|
| Departmental sales dashboard, under 500K opps, daily refresh | Dataverse connector (Import) |
| Cross-module executive dashboard, 2M+ rows, hourly refresh | Fabric Link (preferred) or Synapse Link |
| Operational view of today's cases for a service manager | Dataverse connector (DirectQuery), narrow query |
| Paginated invoice or statement report | TDS endpoint |
| Real-time service desk wallboard | Streaming dataset fed by Power Automate |
| Mixing CRM + ERP + finance data in one semantic model | Fabric Link, joined to other Fabric sources |
If you're already on Microsoft Fabric, default to Fabric Link. The combination of zero-copy OneLake shortcuts, DirectLake semantic models, and Purview governance is a meaningfully better architecture than scheduled refreshes hitting the Web API.
Quick answer: Dataverse stores CRM data as a normalised relational model with GUID lookups between entities. Power BI performs best on a star schema with one or two large fact tables and conformed dimensions. The translation work (flattening lookups, exploding option sets, deciding what's a fact versus a dimension) is the unglamorous part of any D365 Power BI project, and the part that most often gets done badly.
Here's the gap. Dataverse's data model (Common Data Model schema) is highly normalised: an Opportunity has a parentaccountid GUID lookup to Account, an ownerid lookup that's polymorphic (can be a User or a Team), an opportunityid primary key, and dozens of option set columns where the value stored is an integer code. Drop the raw tables into Power BI and you'll get a snowflake. It's joinable, but slow and confusing for users.
A working CRM star schema typically has four fact tables: Fact_Opportunity (grain: one row per opportunity), Fact_Activity (one row per call, email, or task), Fact_Case (one row per case, with SLA timing measures), and Fact_Pipeline_Snapshot (one row per opportunity per day, which is what gives you historical pipeline movement). Dimensions usually include Dim_Account, Dim_Contact, Dim_User (flattened from SystemUser with manager hierarchy), Dim_Product, Dim_Date (build your own, don't use the Dataverse one), and Dim_Stage from the Process Flow entity.
If star schemas aren't second nature, our Power BI relationships and semantic model guide covers cardinality, filter direction, and why bidirectional filters get expensive on CRM models specifically.
Option sets (status, priority, industry, customer type) are stored as integer codes in Dataverse, with display labels held separately in metadata. Power BI doesn't see the labels by default, so your report shows "1, 2, 3" for opportunity status instead of "Open, Won, Lost."
Two ways to handle this. Either bring in the StringMap and globaloptionsetmetadata system tables, join on attributename and objecttypecode, and materialise a small dimension table per option set. Or use the formatted-value column: when you load an entity, Dataverse offers both statuscode and statuscodename, and the name suffix gives you the label. Simpler, but the column sorts alphabetically, so "Lost" appears before "Open" appears before "Won." For most sales dashboards, the formatted-value approach with explicit DAX sort columns is fine. For data science work where you need stable codes, build the dimension tables.
The ownerid column on most Dataverse entities is polymorphic: it can point to a User or a Team. The same applies to regardingobjectid on Activity entities (Account, Contact, Opportunity, or Case). Power BI cannot create a single relationship from a polymorphic column to two different dimensions. The workaround is to split the column into owner_user_id and owner_team_id at load time (using the _ownertype column Dataverse exposes), create separate relationships to Dim_User and Dim_Team, and write DAX measures that handle both branches with COALESCE. Fiddly, and most Microsoft tutorials skip it, so it's the kind of thing that breaks dashboards in production six months in.
Quick answer: Sales pipeline dashboards built on D365 data have four reusable components: opportunity funnel, win rate by stage and segment, forecast accuracy versus close date, and pipeline movement over time. The last one requires daily snapshots of the Opportunity entity. Dataverse alone won't give you historical pipeline.
A canonical D365 sales dashboard has four pages: current pipeline state (open and weighted pipeline, funnel by stage, top opportunities), velocity (stage-to-stage conversion, average days in stage, stalled opportunities older than 30 days), forecast accuracy (forecast versus actual by sales manager, probability calibration, slip analysis), and a pipeline movement waterfall.
The pipeline movement view is where most D365 dashboards fall short. Dataverse holds only the current state of every opportunity, so for "what did my pipeline look like a month ago," you need a snapshot. Two ways to build it: Fabric Link with point-in-time queries against the Opportunity delta history (cleanest), or a scheduled daily job that writes the full Opportunity table into a Fact_Pipeline_Snapshot table keyed by snapshot date (older pattern, still works). If you're on the native Dataverse connector with no Synapse or Fabric in the picture, snapshots are not practical at scale. That's a signal to upgrade the connection method.
Quick answer: Customer service dashboards built on D365 Case data focus on three things: SLA performance (first response and resolution), case volume and backlog by team and channel, and customer satisfaction trends. The model is simpler than sales. One fact table (Case) with date, owner, account, and product dimensions.
A canonical D365 service dashboard has three pages. Operational KPIs covers open cases by priority and queue, SLA breach for the current month, average first response and resolution time, and backlog buckets at 7, 14, and 30 days. Team performance covers cases handled per agent, resolution time distribution, first-contact resolution rate, and reassignment counts. Customer experience covers CSAT trend (if you capture it as a custom entity post-resolution), cases by product (the "what's breaking" view), and repeat callers (accounts with more than 3 cases in 30 days).
Two modelling decisions matter most. D365's SLA engine writes firstresponsesentondate and resolvebydate onto the Case entity; use those, but compute breach status yourself in DAX so the dashboard works regardless of whether the SLA engine is current. And Dataverse stores all timestamps in UTC, so convert to local time (Gulf Standard Time / Arabia Standard Time for GCC clients) in a single Power Query step, not in each visual. Otherwise every measure that touches "today" has a four-hour offset bug.
Quick answer: For Import mode on the Dataverse connector, use incremental refresh keyed on modifiedon. For large environments, move to Fabric Link or Synapse Link and stop managing refresh entirely. Schedule heavy refreshes outside business hours, especially for shared D365 environments.
The default Power BI refresh strategy of full refresh on a schedule works for tiny models and fails everywhere else. For Dataverse, three patterns scale. Configure incremental refresh with RangeStart and RangeEnd parameters filtered against modifiedon; Power BI partitions the table by date and only refreshes recent partitions, turning a 45-minute full refresh into roughly 90 seconds on a 2M-row Opportunity table. For real-time needs, use a composite model: hot data (last 30 days) in DirectQuery against Dataverse, cold data (everything older) in Import mode, with the user seeing one table. For production workloads, plan toward Fabric Link with DirectLake: Dataverse pushes changes into OneLake continuously, DirectLake models read without import or query, and refresh stops being your problem.
Two operational rules. Schedule heavy refreshes between 02:00 and 05:00 local time. Don't refresh more than twice per hour on the native Dataverse connector or API throttling kicks in. Watch Dataverse API entitlement consumption in the Power Platform admin centre, since heavy Power BI refreshes show up there.
Quick answer: Power BI does not automatically inherit Dataverse security. You must either re-implement row-level security in the Power BI model (Import mode), use DirectQuery against Dataverse with SSO so Dataverse enforces it (slow but accurate), or limit Power BI consumption to roles that should see everything.
This catches teams who assume Power BI dashboards on D365 data automatically respect each user's Dynamics permissions. They don't.
The three workable patterns. Re-implement RLS in Power BI by building a user-to-territory or user-to-business-unit mapping table, then writing a DAX security filter against USERPRINCIPALNAME(); duplicates rules but performs well. Use DirectQuery with SSO so queries go to Dataverse under the user's own credentials and Dataverse applies its own security model; most accurate, slowest, practical only for narrow operational reports. Or restrict consumption: make the Power BI workspace available only to roles that should see all data (sales leadership, finance, ops), and give the limited view to limited audiences via a different report.
For enterprise GCC clients, this often combines with Power BI's on-premises data gateway in GCC environments when the architecture mixes cloud Dynamics with on-prem ERP data behind the corporate firewall.
A first-version sales dashboard on the native Dataverse connector takes 5 to 10 working days end-to-end for an experienced team. A full Fabric Link or Synapse Link deployment with snapshotting, RLS, cross-module reporting, and governance setup is typically 4 to 8 weeks. The bottleneck is usually not the tooling. It's data quality (duplicate accounts, incomplete opportunity stages) that needs cleanup before the dashboards become trustworthy.
The Power Query Dataverse connector works only with Dynamics 365 online. For on-premises Dynamics 365 Customer Engagement, you connect via the SQL Server connector against the CRM database directly. On-prem customers should also evaluate moving to cloud Dynamics 365, since Synapse Link and Fabric Link only work against cloud Dataverse.
No, not directly. Power BI is a read-only reporting layer. If you need write-back patterns, you build a Power Apps canvas app embedded in the Power BI report: the user clicks a row in Power BI, the canvas app opens with that opportunity's details, and updates write back via the Dataverse Web API. Common pattern, but you're now maintaining two artefacts.
Business Process Flows write to their own entity, one per BPF. To analyse stage durations, join the BPF entity to its parent record (Opportunity, Lead) and extract stage transitions from the activestageid history. The catch is that history of stage changes is not directly exposed and typically requires either Synapse Link history or a custom audit pipeline. For most teams, the simpler approach is to materialise the current stage onto the Opportunity itself.
The native Dataverse connector costs nothing beyond the Power BI licences users already need. Fabric Link requires a Fabric capacity: the entry F-SKU (F2, F4) starts around $260 to $520 per month list price in 2026, paid on top of Dynamics 365 and Power BI licences. For a 100-user CRM with 2M+ opportunities and frequent refresh, the Fabric cost is usually justified. For a 20-user CRM with 50K opportunities, it isn't.
Yes. Both the native Dataverse connector and Synapse Link / Fabric Link expose all custom entities and fields in the environment. The schema name (logical name, with the publisher prefix) is what you'll see in Power BI: new_dealsize rather than the display name "Deal Size." Rename in Power Query for readability.
Quick answer: For a small environment and a single dashboard, the native Dataverse connector plus a weekend of Power Query work will get you most of the way. For a 500-seat enterprise build with cross-module reporting and GCC data residency requirements, the connection method, semantic model, and refresh architecture are decisions that stay with you for years and are worth getting right up front.
At enterprise scale, with multiple business units, historical pipeline, and PDPL or NDMO compliance in scope, refactoring at year two costs significantly more than designing carefully at the start.
Beyond The Analytics builds Power BI semantic models on Dynamics 365 and Dataverse for GCC enterprises. If you'd like to talk through an architecture for your environment, get in touch.
Microsoft Partner · Dubai
Your business intelligence partner for the GCC
Have a data challenge or a project in mind? Reach out and let's explore how we can help.
Clients we've worked with






