D365 Configurable Entity/Record Cloner
This solution allows for configurable cloning of entities/records, link-entities (child), relationships, associations, connections and attributes, in Dynamics 365, at any depth level.
Description
There are sometimes use cases where cloning of record(s), relationship(s) and associaton(s) is required.
The idea behind this solution came as a result of some requirements needed for a project, where various record cloning functionalities with specific rules were required, such as cloning an account with some fields, associated contacts, annotations, etc.
The goal was to have a single component with the ability to configure cloning information and thus provide a No-Code/Low-Code solution.
Features
- Configurable cloning of entities and sub-entities (child entities).
- Configurable cloning of associations (n:m relationships).
- Configurable cloning of connections and connected records.
- Configurable set of fields to be cloned.
- Automatically skipping of fields not valid for being set in creation.
- Modularization of configurations (splitting).
Getting started
Prerequisites
Dynamics 365 v9.2+
Install
Download the managed or unmanaged solution and import it in your environment.
Configure
-
Create a configuration record for each needed cloning functionality.
-
Provide a meaningful name.
-
Provide the xml representing entities, filters, relationships, associations, attributes, etc. for cloning a specific entity.
- I recommend to use FetchXml Builder to build the FetchXml.
-
The root entity cannot be an intersect-entity (entity for the n:m relations).
-
The attribute link-type in the FetchXml has no effect:
- The action always applies an outer join.
-
The cloner module skips all the system fields not valid for creation (i.e.: createdon, createdby, statecode, etc.).
Sample Config Xml
<fetch>
<entity name="account" >
<attribute name="name" />
<attribute name="accountnumber" />
<filter>
<condition attribute="statecode" operator="eq" value="0" />
</filter>
<link-entity name="new_new_ddentity_account" from="accountid" to="accountid" intersect="true">
<attribute name="new_ddentityid" />
<attribute name="accountid" />
<link-entity name="new_ddentity" from="new_ddentityid" to="new_ddentityid" intersect="true" clone-behaviour="clone">
<attribute name="new_name" />
<filter>
<condition attribute='statecode' operator='eq' value='0' />
</filter>
</link-entity>
</link-entity>
<link-entity name="contact" from="parentcustomerid" to="accountid" >
<attribute name="address1_city" />
<attribute name="address1_country" />
<attribute name="address1_line1" />
<link-entity name="annotation" from="objectid" to="contactid" >
<attribute name="filename" />
</link-entity>
</link-entity>
<link-entity name="phonecall" from="regardingobjectid" to="accountid" >
<attribute name="subject" />
<filter>
<condition attribute='statecode' operator='eq' value='0' />
</filter>
</link-entity>
</entity>
</fetch>
Config Xml - Properties
- clone-behaviour (required only for intersect-entity): associate/clone
- clone -> the associated entity is cloned and the clone is associated to the new parent
- associate (default) -> the associated entity is not cloned: it is associated to the new parent
Config Xml - Connections
The connections (entity connection) are represented with the following xml-block:
<fetch>
<entity name="connection">
<attribute name="record1roleid" />
<attribute name="record2roleid" />
<attribute name="record2id" />
<attribute name="record1id" />
<attribute name="record2objecttypecode" />
<attribute name="record1objecttypecode" />
<filter>
[<condition attribute="record1roleid" operator="eq" value="<Id of the Role1>" />]
[<condition attribute="record2roleid" operator="eq" value="<Id of the Role2>" />]
</filter>
<link-entity name="contact" from="contactid" to="record2id">
<attribute name="address1_composite" />
<attribute name="firstname" />
<attribute name="fullname" />
</link-entity>
<link-entity name="account" from="accountid" to="record1id">
<attribute name="address1_composite" />
<attribute name="name" />
</link-entity>
</entity>
</fetch>
- the following attributes for the connection entity are all required:
- record1roleid
- record2roleid
- record2id
- record1id
- record2objecttypecode
- record1objecttypecode
- the two linked entities are necessary for providing information of the entities that are connected and will be cloned
- in the filter for the connection entity we can insert the id of the role1 and role2 for which we want to look
- the associated entities (xml-blocks for record1 and record2) do not support filters (for the moment)
- connections do not support link-entity (for the moment): a connection is always the leaf of a branch in the xml
Config Xml - Modularization
It is possible to modularize Config-Xml to allow splittig of configurations and reuse the single configuration. Below an example of config modularization:
- Config1
<fetch>
<entity name='contact' >
<attribute name='firstname' />
<attribute name='lastname' />
<filter>
<condition attribute='statuscode' operator='eq' value='1' />
<condition attribute='contactid' operator='eq' value='@id' />
</filter>
<link-entity name='annotation' from='objectid' to='contactid' >
<attribute name='subject' />
</link-entity>
</entity>
</fetch>
- Config2
<fetch>
<entity name='account'>
<attribute name='address1_composite' />
<attribute name='name' />
<filter>
<condition attribute='accountid' operator='eq' value='@id' />
</filter>
<link-entity name='contact' from='parentcustomerid' to='accountid' merge-config-id='{config1.Id}' />
</entity>
</fetch>
- The Config1 can be used stand-alone or can be integrated into another Config-Xml (in the example, Config2).
- The attribute merge-config-id contains the Guid of the Config-Xml that has to be integrated in the Config2 (in the example, Config1).
- After merging, the config-xml looks like following:
<fetch>
<entity name="account" >
<attribute name="address1_composite" />
<attribute name="name" />
<filter>
<condition attribute="accountid" operator="eq" value="@id" />
</filter>
<link-entity name="contact" from="parentcustomerid" to="accountid" >
<attribute name="firstname" />
<attribute name="lastname" />
<filter>
<condition attribute="statuscode" operator="eq" value="1" />
</filter>
<link-entity name="annotation" from="objectid" to="contactid" >
<attribute name="subject" />
</link-entity>
</link-entity>
</entity>
</fetch>
Usage
- Integrate the action CloneEntityFromFetch where you want to trigger it (Workflow, Javascript function, etc.).
- Configure the parameters:
- RootRecordInfo:
- Url of the root entity (parameter Record Url (Dynamic) in Workflow configuration) or
- Guid of the root entity as string
- EntityName: schema name of the root entity. This parameter is required if the parameter RootRecordInfo is a simple guid (not an url).
- Configuration: EntityReference to the configuration record.
- RootRecordInfo:
Note
- This solution is work in progress:
- it will be update frequently
- it is not fully tested: if you find any bug, please open an issue or push a fix on a new branch
- Technically the solution should allow an unlimited number of levels for link-entities, however always consider the 2-minute limit for running Plugins/CWAs. Analyze the amount of data involved in fetching.
Back matter
Acknowledgements
Thanks to all who helped inspire this solution.
License
This project is licensed under the GPLv3.