Dual-Write vs Virtual Entities vs OData: Choosing the Right F&O–Dataverse Pattern
Finance and Operations and Dataverse often hold different parts of the same business process, but the wrong integration pattern turns a clean design into a support queue. Teams usually argue about tools before they agree on latency, ownership, and failure handling. The right choice is not the newest connector; it is the pattern whose failure mode the business can live with.
Dual-write is a product feature, not a generic sync engine
Dual-write is best when the same business concept must exist in both apps with near real-time movement. It uses Microsoft supplied and custom table maps to synchronize rows between Finance and Operations and Dataverse. That makes it attractive for customer, vendor, product, contact, and party scenarios where users expect both sides to agree quickly.
The strength is also the constraint. Dual-write assumes you are mapping Dataverse tables and Finance and Operations data entities that Microsoft has made suitable for live synchronization. You get orchestration, initial sync, table map dependencies, and error handling in the dual-write runtime, but you also inherit its opinions about shape, keys, and sequencing.
Choose dual-write when both systems need durable data. If Sales creates an account and Finance needs the customer quickly, dual-write fits. If Finance owns released products and Field Service needs product data for work orders, dual-write can fit. If the data is only needed for occasional lookup, dual-write usually creates unnecessary duplication and operational noise.
Virtual entities are live projection for read-mostly scenarios
Virtual entities expose external data in Dataverse without storing the rows as normal Dataverse records. For Finance and Operations scenarios, the practical benefit is a live view into Finance and Operations data from model-driven apps, lookups, and forms. Users see current values without a sync backlog, storage growth, or reconciliation process.
That sounds ideal until someone tries to use virtual data like local data. Virtual entities are slower than local Dataverse tables because each read crosses a boundary. Query support can be limited by the provider. Offline, auditing, rollup, and workflow behavior may not match normal tables. Writes are possible only when the provider and entity support them, and even then you should treat the pattern as read-mostly.
Use virtual entities for reference data and operational visibility. Examples include showing inventory availability, purchase order status, credit hold state, or invoice history inside a customer service app. Do not use them for high-volume grids that users filter constantly, or for transactional logic that requires Dataverse plug-ins to update the projected row.
OData and DMF are integration tools with different clocks
Finance and Operations OData endpoints are useful for service-to-service operations where the caller needs a relatively direct API. The Data Management Framework, usually called DMF, is better for file and batch movement. Both are valid, but they solve different timing problems.
OData fits moderate volume, API-driven integration. A Dataverse plug-in or Azure Function can call Finance and Operations to create a sales order, validate a customer, or retrieve a status. The caller must handle throttling, retries, authentication, and partial failures. For large loads, OData can become expensive because every row behaves like an API transaction.
DMF fits bulk import and export. It is deliberately asynchronous and operational. You stage data packages, run jobs, monitor execution, and handle errors in batches. That is exactly what you want for nightly pricing updates, large customer migrations, historical invoices, or master data loads where a one-minute latency target is fake precision.
{
"integration": "finance-price-export",
"pattern": "dmf-batch",
"schedule": "0 2 * * *",
"retryPolicy": {
"maxAttempts": 3,
"backoffSeconds": 300
},
"owner": "finance-platform"
}
Latency should be designed before fields are mapped
Latency is not just a performance number. It defines user expectation, support behavior, and the kind of reconciliation you need. If a sales user edits an account and Finance is expected to block invoicing within seconds, you need a near real-time pattern. If the warehouse only needs updated discount groups before tomorrow morning, a batch is safer and easier to govern.
| Pattern | Best latency | Direction | Volume fit | Primary failure mode |
|---|---|---|---|---|
| Dual-write | Seconds to minutes | Bidirectional or configured one-way maps | Medium operational data | Map errors, dependency failures, stuck sync |
| Virtual entities | Live read at request time | Usually external to Dataverse | Read-heavy, low to moderate grids | Slow reads, provider limits, unavailable source |
| OData | Request time | Usually caller-directed | Low to moderate transactions | API throttling, partial transaction failure |
| DMF batch | Minutes to hours | Import or export job | High volume and migration loads | Batch rejection, staging errors, delayed correction |
The trap is forcing every integration into the lowest possible latency. Fast integration is not automatically better. It is more coupled, more expensive to operate, and less tolerant of source system downtime.
Ownership decides the direction of truth
Before choosing a connector, write down the system of record for each field. Not each table: each field. Customer name may be mastered in Dataverse while credit limit is mastered in Finance and Operations. Product description may come from Finance, while sales collateral lives in Dataverse. This field-level ownership prevents circular updates and makes support decisions obvious.
Use one-way maps where possible. Bidirectional sync sounds democratic, but it increases conflict paths. If both systems can update the same attribute, decide which update wins, how conflicts are detected, and who owns correction.
Separate reference from transaction data. Reference data can tolerate scheduled updates or projection. Transaction data usually needs clear command ownership. For example, creating an order in Dataverse and creating a sales order in Finance and Operations are not the same event unless the business accepts the same validation rules and failure handling.
Design idempotency early. Every integration eventually retries. Use stable alternate keys, external correlation IDs, and status tables so a retry updates the same logical request instead of creating another row.
CREATE TABLE IntegrationRequest (
CorrelationId nvarchar(100) NOT NULL PRIMARY KEY,
SourceSystem nvarchar(40) NOT NULL,
TargetEntity nvarchar(80) NOT NULL,
TargetKey nvarchar(100) NULL,
Status nvarchar(30) NOT NULL,
LastError nvarchar(max) NULL
);
Failure handling is where architecture becomes real
Dual-write failures are visible in dual-write monitoring and often relate to missing dependencies, invalid mappings, or data validation differences. The support model is map-centric: fix the data or mapping, then replay. You need owners who understand both apps and can read the error, not just restart the job.
Virtual entity failures are user-facing. If Finance and Operations is slow or unavailable, the Dataverse form may be slow or empty. That is acceptable for visibility scenarios, but dangerous for core processes. A good virtual entity design includes fallback messaging and avoids placing the projected data in the middle of a save transaction.
OData failures belong to the caller. If an Azure Function creates a customer and then fails while writing the Dataverse status, you own the compensation logic. For DMF, the failure is usually batch operational: rejected package, staging validation, or records in error. That requires monitoring, reprocessing, and business-friendly error reports.
$job = @{
Name = "NightlyCustomerExport"
Pattern = "DMF"
MaxRetry = 3
AlertChannel = "FinanceOps"
}
$job | ConvertTo-Json
The practical selection rule is boring and reliable
Start with the business question. If both systems must own and act on the data quickly, evaluate dual-write first. If Dataverse users only need to see Finance and Operations data, prefer virtual entities. If another service is commanding a specific transaction, use OData with idempotent design. If the requirement is volume, reconciliation, or scheduled movement, use DMF.
Do not let a pattern solve a problem you have not named. Dual-write is powerful when the data model fits. Virtual entities are elegant when users can tolerate live dependency. OData is precise when calls are small and controlled. DMF is dependable when volume matters more than immediacy. The senior move is choosing the failure mode before choosing the connector.
Keep reading
Dual-Write Between D365 F&O and Dataverse: Pitfalls and Patterns That Actually Work
A field-tested guide to dual-write configuration between D365 Finance & Operations and Dataverse — covering initial sync failures, conflict resolution, performance tuning, and when to use alternatives like virtual entities or data export service.
Extending Data Entities in D365 Finance & Operations Without Breaking Upgrades
Add fields, computed columns, and validation to standard D365 Finance & Operations data entities the upgrade-safe way — with X++ examples and the staging-table traps to avoid.
Chain of Command vs Event Handlers: Extending D365 F&O the Right Way
When to use Chain of Command and when to use pre/post event handlers in Dynamics 365 Finance & Operations — with X++ examples, a decision table, and the gotchas that trip up teams.
Newsletter
New posts, straight to your inbox
One email per post. No spam, no tracking pixels, unsubscribe anytime.
Comments
No comments yet. Be the first.