- Published on
- min read
- 4
- About
- Ismael Baddich
Migrating an on-premise CRM to Dynamics 365 without losing your data
Lessons from a banking migration off CRM 2015 on-premise onto Dynamics 365: mapping, Azure Data Factory pipelines, reconciliation, and the traps that cost real money.
A CRM migration is judged on a single number: how much data did you lose? On a project moving a bank off Microsoft CRM 2015 on-premise onto Dynamics 365, that number was 0.28% — and every one of those records was identified, documented and signed off with the business before go-live.
Here’s the method that got us there.
Start with the mapping, not the tool
The most common mistake is opening Azure Data Factory on day one. A badly targeted pipeline will migrate wrong data very efficiently.
Before writing a single line, three questions need answers from the business:
- Which entities are actually in use? On a ten-year-old CRM, it’s normal to find that 30% of custom entities hold nothing alive. Don’t migrate your technical debt.
- What are the dependencies? Accounts before contacts, contacts before opportunities. An explicit dependency graph determines pipeline order.
- What is authoritative? If a field exists in the CRM and in a third-party system, decide now which one wins. Not during cutover weekend.
This phase produces a mapping document: source entity, target entity, field by field, transformation rule, business owner. It’s tedious. It’s also the difference between 0.28% and 8%.
Azure Data Factory for extract and load
Once the mapping is frozen, ADF does the heavy lifting. The structure that works:
- Extract: one pipeline per source entity, copying into a SQL staging area. No transformation here — you want a faithful, timestamped, replayable copy of the source.
- Transform: cleanup logic in SQL, inside staging. Deduplication, format normalisation, resolving lookups to the new GUIDs.
- Load: write into Dataverse.
Splitting into three distinct stages isn’t cosmetic. When a transformation rule changes three days before go-live — and it will — you replay Transform and Load without re-pulling the source for six hours.
Keep staging online after go-live. For weeks afterwards, every “where did this record come from?” question is answered with one SQL query.
PowerShell for what ADF does badly
ADF is excellent at volume. It’s painful for fine-grained work: recreating a handful of records, fixing a lookup, replaying one specific batch.
For that, PowerShell with PsDataverse is far faster to write. The scripts that earned their keep:
- Source/target record reconciliation with the deltas exported to CSV
- Bulk owner reassignment after import
- Targeted deletion of a failed batch, by batch id
One thing matters more than the rest: tag every migrated record with a batch id and its source key. A custom text field is enough. Without it, partial rollback is impossible and you replay everything.
Reconciliation is not optional
It’s the step compressed first when the plan slips, and that’s a mistake.
Three levels of control, cheapest to most expensive:
| Level | Control | Catches |
|---|---|---|
| 1 | Record counts per entity, source vs target | Missing batches |
| 2 | Checksums on numeric and date fields | Broken transformations |
| 3 | Random sample validated by hand with the business | Mapping misunderstandings |
Level 1 is automatable and replays on every iteration. Level 3 rarely finds much — but what it finds is usually a misunderstanding of the business domain, the kind of error that shows up in no log anywhere.
The traps that cost money
GUIDs don’t survive. Dataverse generates new identifiers. Any logic referencing a hard-coded GUID — integration, report, Word template — breaks. Inventory them beforehand, not afterwards.
Dates have no timezone. CRM 2015 on-premise often stores server local time; Dataverse works in UTC. One missed conversion shifts an entire database by an hour or two. It’s silent, and nobody notices until the first monthly report.
Calculated fields recalculate. If your source holds a frozen historical value in a field that is calculated in the target, Dataverse will overwrite it. Migrate into a simple field, or accept losing the history.
Plugins fire on import. Unless explicitly disabled, every create triggers your plugins, workflows and Power Automate flows. On 400,000 records, that means 400,000 notification emails. Disable, import, re-enable — and test that the re-enable actually worked.
What to take away
A successful migration is boring. No heroics on cutover weekend, no improvised fixes at 3am. It’s boring because the hard work — the mapping, the transformation rules, the reconciliation — happened weeks earlier, in a spreadsheet nobody wanted to read.
That’s where the 0.28% is won.
Related articles
- Dynamics 365Power Platform
Plugin, Power Automate or business rule? Choosing the right tool in Dynamics 365
The same requirement can be built four different ways in Dynamics 365. A practical decision tree based on transactions, latency and the real cost of maintenance.
4 min read - Power PlatformDataverse
Dataverse: five design mistakes that cost you later
The modelling decisions made in week one of a Power Platform project are the ones you pay for two years later. Here are the five I see most often.
3 min read