MSDyn365 Internals: Merge Permissions

The ability to merge records is a powerful one in keeping control of your data quality within PowerApps / D365. It can also be deceptively complex. As we’ve been living & breathing this area of the platform for several years during the development of data8 duplicare, we’ve seen a lot of unexpected things show up when merging particular records. To help understand why you might get an error when merging, I’ve tried to “deconstruct” the merge process below.

Standard Microsoft Dynamics 365 Merge screen

Basic Validation

There’s a whole bunch of conditions that must be met before two records can be merged. If these aren’t met, you should get an error message back pretty quickly. Most of these errors are quite self-explanatory, but there are some that took a little more digging to get to the bottom of.

  1. You can’t merge a record with itself. Kind of goes without saying, but we had to check! If you try and merge a record with itself you’ll get the error Merge cannot be performed on master and sub-entities that are identical.
  2. You can only merge accounts, contacts, leads and cases. Try to merge anything else and you’ll get the error This type: {name} is not supported with merge operation
  3. Both the master and subordinate records must be active. If you try to merge an inactive record you’ll get one of the following messages:
    • master entity:{name}-{id} is deactive
    • sub-entity:{name}-{id} is deactive
  4. Security checks. You need several different security privileges to be able to merge:
PrivilegeRequired On
ReadMaster Record
WriteMaster & Subordinate Records
ShareMaster Record
Append ToMaster Record
MergeGlobal

If you’re missing any of these you’ll get the rather ambiguous message “Merge is not allowed: caller does not have the privilege or access”. You can use a tool use as the XrmToolBox Access Checker plugin to find out exactly which privilege you are missing. If you’re on-prem then you can take a look at the server trace logs to get more details.

Access Checker plugin in XrmToolBox

Advanced Validation

Now we’ve got the basic sorted, there are some more detailed validation checks that the system will run depending on the type of records being merged:

  1. You can’t merge cases with different parents. If you try you’ll get the error “Child cases having different parent case can not be merged.”
  2. You can’t merge accounts or contacts with different parents unless you specify the PerformParentingChecks parameter as false. Otherwise you’ll get one of the following messages:
    • Merge warning: sub-entity might lose parenting (if the subordinate record has a parent but the master doesn’t)
    • Merge warning: sub-entity will be differently parented. (if the master and subordinate records both have different parents)
  3. You can’t merge cases that have more than 100 child cases between them. There is a system-defined limit of 100 child cases per parent case, so if you try to merge two cases that each have 60 child cases, that would result in the final merged case having 120 children, which isn’t allowed. This triggers the error message A Parent Case cannot have more than 100 child cases. Contact your administrator for more details
  4. You can’t merge accounts or contacts that have an active quote. Specifically, the subordinate record can’t have an active quote, but the master record can. If you try to merge two records where the subordinate has an active quote you’ll get the error Merge cannot be performed on sub-entity that has active quote.
  5. You can’t merge records if that would create a loop in a hierarchy. For example, if you have a hierarchy of accounts A <- B <- C, and try to merge A and C. That could result in the parent of A being B (copying the parent from C), and the parent of B being A. If you end up in this situation you’ll get one of the following errors:
    • Merge could create cyclical parenting. (account)
    • Loop exists in the contacts hierarchy. (contact)
    • Creating this parental association would create a loop in Contacts hierarchy. (contact)

Move Related Records

Now it’s done all the up-front checks, it’s time to move onto the interesting bit – moving the records that were related to the subordinate record over to the master.

At this point the system goes through all 1:N and N:N relationships with a Merge cascade type of Cascade All (note that you can’t change this cascade type – it will typically be Cascade All except for a few system-managed relationships).

All records related to the subordinate record through one of these relationships will be updated to refer to the master record. This requires Append and Write privileges on the related record. If you don’t have those privileges you’ll get an error like SecLib::AccessCheckEx2 failed. Entity Name:activitypointer,OwnershipTypeMask:UserOwned, ObjectId: 98dbe456-43b3-e711-80e6-3863bb35af60, OwnerId:843ef80e-6a51-e511-9759-f0921c100524, OwnerIdType:8, OwnerData: roleCount=2, privilegeCount=2993, accessMode=0 and CallingUser:100781f5-c4d6-e311-aec5-d89d6764507c, CallerBusinessId:0c92636a-d920-e511-b427-d89d67632c70 PrincipalData: roleCount=5, privilegeCount=1141, accessMode=0. ObjectTypeCode:4200, ObjectBusinessUnitId:0c92636a-d920-e511-b427-d89d67632c70, AccessRights: WriteAccess. Computed rightsToCheck=WriteAccess, grantedRights=8, hsmGrantedRights=None, grantedRightsWithHsm=8,

The highlighted parts help you identify the type of record and the ID of the individual record that the user is missing permissions on, and the permission that is missing.

It will also update any activities that have the subordinate record as a party (sender, to, organizer etc.) to use the master record instead, even though the Activity Party relationships have a Merge cascade type of Cascade None.

Each of these updates can in turn trigger workflows or plugins that can have any number of other side effects, so it’s quite common to see other errors here. It can be difficult to debug these – the simplest way beyond trial and error is to get access to the trace logs which should tell you what entity type and record ID is triggering the error.

If you find that merging is taking a long time, it’s normally this step that’s the problem. Check your foreign key indexes, synchronous plugins and workflows.

Sharing

The next non-obvious part of the process is that any related records that have been moved from the subordinate to the master will now be shared with the owner of the master record. For example, if you merge two accounts, the contacts that were associated with the subordinate account will be shared with the owner of the master account. This tends to make things “just work” as most users would probably expect, but may break your security model. If you want to disable this behaviour you can do so by changing the GrantFullAccessForMergeToMasterOwner organisation setting to False.

Similarly, the master record will be shared with the owner of the subordinate record. To turn this off, use the GrantSharedAccessForMergeToSubordinateOwner organisation setting.

Finally, reparenting rules are applied. Any 1:N relationships with a Reparent cascade rule will be processed to share the related records with the owner of the master record.

Updating the Master

Any updates to the master record are applied, e.g. if you want to take data from the subordinate record that is missing or incorrect on the master.

In the merging UI in the web application you can only select values to update that are in the same field on each record, i.e. use Email Address 1 from either the master or subordinate. Using the SDK however, there’s nothing to stop you taking values from elsewhere, or even just making up entirely new values that don’t exist in either record, by populating the UpdateContent parameter of the MergeRequest.

Deactivating the Subordinate

The subordinate record is linked to master record and deactivated. The state code and status code of the subordinate will be set as follows:

Entity TypeState CodeStatus Code
Account1Default
Contact1Default
Lead2Default
Case22000

This can trigger an error when restricted state transitions have been set up for cases: “The merge couldn’t be performed. One or more of the selected cases couldn’t be cancelled because of the status transition rules that are defined for cases.”

The default status code for a state code can be selected within the attribute customization screen, currently this can only be done through the classic UI:

Version 9 Changes

Merging has stayed essentially unchanged since at least version 4, but version 9 introduced a few new standard plugins that attach to the Merge message and can trigger some different error messages.

In the Move Related Records section above, we saw the rather unhelpful error message that was returned when the user doesn’t have permissions to move one of the related records from the subordinate to the master record. Version 9 introduces a new plugin Microsoft.Dynamics.Sales.Plugins.PreOperationAccountMerge, and while it’s not immediately clear what this plugin does, it does generate a more helpful version of the same error:

Principal user (Id= 
100781f5-c4d6-e311-aec5-d89d6764507c , type=8) is missing prvReadCustomerOpportunityRole privilege (Id=db84e2ea-44fe-44ff-b01c-bd1b3d3d07ae)

Rather less helpfully, it also introduces plugins for the Microsoft Dynamics for Marketing product. One of these, Microsoft.Dynamics.Marketing.Plugins.PreDisassociateEntitiesPlugin, will commonly trigger errors when the subordinate record being merged is a member of a marketing list. If you receive an error such as:

Disassociation cannot be performed between an entity of type list and an entity of type account.; [Microsoft.Dynamics.Marketing.Plugins: Microsoft.Dynamics.Marketing.Plugins.PreDisassociateEntitiesPlugin]

then you have hit the bug in this plugin. There isn’t anything wrong with your process, code or data, but to work around the bug you’ll need to first remove the subordinate record from any marketing lists then merge the records again. If you want to preserve the marketing list membership, you’ll need to manually add the master record to the same marketing lists as the subordinate record was part of.

Leave a comment

Your e-mail address will not be published. Required fields are marked *