How to handle deletions
In part 1 of this series , we learned how to avoid some key limitations by using PowerShell instead of the Configuration Migration Tool. We created a simple system to import and export records to JSON files.
However, there are still some more important limitations in the CMT which are still present in that solution.
Clive Oldridge kindly pointed out the main limitation in a comment:
” […] main issue is deleting isn’t included so you end up with a growing inactive heap of tech debt. “
This is true! both the CMT and our solution so far are additive only - they will only ever create or update records, but not delete them when they are no longer part of the data we a importing. This means our target environment will end up with extra records left behind in the “heap” Clive is describing and potentially causing mischief.
Deactivating records is an alternative solution which can work instead of deletion
The solution we have so far will export and import the statuscode
of the records. So any records that we deactivate before we export, will get deactivated in each environment we import to.
Deactivation can be a simpler solution that deletion for some use-cases because we don’t need to worry about any existing data in other tables that is linking to the records we want to delete.
Understanding deletions in Dataverse
If you are going to go with deletions make sure you understand about the relationships behaviours settings that exists on each Dataverse relationship which defines what you want to happen when a related record is deleted. Also, don’t forget to make sure your application hides the inactive records!
Before we work out how to resolve this, as a reminder, here’s an example export script for two tables:
$ErrorActionPreference="Stop"
install-module -scope CurrentUser Rnwood.Dataverse.Data.Powershell
$connection = get-dataverseconnection -url https://org60220130.crm11.dynamics.com/ -interactive
Get-DataverseRecord -connection $connection -TableName myorg_categoryl1 | Set-DataverseRecordsFolder data/myorg_categoryl1
Get-DataverseRecord -connection $connection -TableName myorg_categoryl2 | Set-DataverseRecordsFolder data/myorg_categoryl2
And here’s the matching import script assuming myorg_categoryl2
records have a relationship to depend on myorg_categoryl1
records:
$ErrorActionPreference="Stop"
install-module -scope CurrentUser Rnwood.Dataverse.Data.Powershell
$connection = get-dataverseconnection -url https://org60220130.crm11.dynamics.com/ -interactive
# Import data to each table in dependency order
Get-DataverseRecordsFolder data\myorg_categoryl1 | Set-DataverseRecord -Connection $connection -Verbose
Get-DataverseRecordsFolder data\myorg_categoryl2 | Set-DataverseRecord -Connection $connection -Verbose
Whenever we export the data, we will make our script compare the records that were exported with the last set that was exported. Any records we don’t find must have been deleted.
We will log the deletions by moving the relevant files in a deletions
subfolder.
When we import the data, we will use the files in deletions
to delete any matching records (if they still exist).
Note:
You’ll need at least version 1.0.14 of Rnwood.Dataverse.Data.Powershell
for this to work!
We just need to add -withdeletions
to our Set-DataverseRecordsFolder
commands to trigger the magic comparison.
Get-DataverseRecord -connection $connection -TableName myorg_categoryl1 |
Set-DataverseRecordsFolder data/myorg_categoryl1 -withdeletions
Get-DataverseRecord -connection $connection -TableName myorg_categoryl2 |
Set-DataverseRecordsFolder data/myorg_categoryl2 -withdeletions
Now whenever this is run, you’ll see the deletions
subfolder of data/myorg_category1
updated based on any records that have been deleted in your source when compared to the files before it ran.
Now we need to update our import script to make it process the deletions:
# Import data to each table in dependency order
Get-DataverseRecordsFolder data\myorg_categoryl1 |
Set-DataverseRecord -Connection $connection -Verbose
Get-DataverseRecordsFolder data\myorg_categoryl2 |
Set-DataverseRecord -Connection $connection -Verbose
# Now process the deletions in reverse dependency order
Get-DataverseRecordsFolder data\myorg_categoryl2 -deletions |
Remove-DataverseRecord -Connection $c -Verbose -IfExists
Get-DataverseRecordsFolder data\myorg_categoryl1 -deletions |
Remove-DataverseRecord -Connection $c -Verbose -IfExists
We need to add another section to the script with a line for each required table:
The -deletions
makes Get-DataverseRecordsFolder
read just the deletions.
These are then piped using |
to Remove-DataverseRecord
to delete them. The -IfExists
there ensures that any records that never or no longer exist (like when this is run again in future) don’t cause an error.
We need to put the lines in reverse dependency order. This makes sure that myorg_categoryl2
records that might reference the l1
records will be deleted first. (If it were the other way around, the remaining l2
records might block the deletion of l1
records that need to be deleted)
This isn’t the only strategy for identifying which records should be deleted. We could instead just list the records in the target environment that don’t match the records we are importing. Now you’re all PS experts, I’m going to leave this up to you if this is what you want.
What other limitations and issues can you think of? Share your questions and experience over at LinkedIn!