Programmatically removing components from a MS Dynamics form or view

The ‘additive’ nature of CRM deployments means that it can be easier to add components to an instance of CRM than it is to remove them. With the additive model, even if you delete a component from your development environment and then export and deploy the solution which contained that component elsewhere, the deleted component will still exist in the target environment. As a result of this, there is a danger that CRM deployments can become littered with redundant components as the cost of the housekeeping required to remove these components from production environments can be seen as quite high.

As part of our Continuous Integration process, we have started to remove redundant artifacts from CRM. For example, attributes, entities and workflows which are no longer needed are removed to keep the solutions as clean as possible. Scripting the housekeeping tasks is actually quite straightforward, and once you have done this a couple of times you will see that it is easy to create a set of standard scripts to remove redundant components.

Removing entities and their relationships can all be scripted using standard calls to the metadata API such as DeleteRelationshipRequest and DeleteEntityRequest. However, these components cannot be deleted if they exist on a form. Therefore if you want to be able to automate the deletion process, you will need to script the removal of the items from the forms on which they appear.

Imagine the scenario where you have two entities in a one-to-many relationship, neither of which is required any more. Entity A is the ‘parent’ of entity B. The Entity A form contains a section with an associated view of Entity B and the Entity B form displays a lookup to Entity B.

To remove the entities manually, the process would be:

  1. Remove any references to either entity from either entity’s forms
  2. Delete the relationship between Entity A and Entity B
  3. Delete Entities A and B

The process has to be carried out in this order because:

  • You cannot delete a custom entity whilst it has a relationship to another custom entity
  • You cannot delete a relationship between entities if that entity is ‘in use’ on a form (either as a lookup for a 1:N relationship or an associated view for a N:1 relationship)

Due to the additive nature of Dynamics, as explained earlier, deleting the entities and then exporting the solution which contained them from our Dev environment and importing into another environment will not result in the entity being deleted from the target organisation. So we will need to write a script to remove the components from our target organisations. Deleting relationships and entities is a straightforward activity and we can easily script this using the DeleteRelationshipRequest and DeleteEntityRequest methods. However, we have to deal with the forms first and even if we do this in our development environment first, this will not help in other environments because the solution we export from Dev will not have our amended forms in them because we have deleted the entities from Dev.

Fortunately, scripting the removal of fields or sections from a form can be done quite easily. We can write a query which retrieves the form xml, identify the xml node which needs removing, update the form xml and publish the new version of the form.

The script will have three stages:

1 – Retrieve the Form XML for the entity in question

            // Get the formxml for the entity
            QueryExpression formQuery = new QueryExpression
            {
                EntityName = "systemform",
                ColumnSet = new ColumnSet(true),
                Criteria = new FilterExpression
                {
                    Conditions =
                    {
                        new ConditionExpression
                        {
                            AttributeName = "objecttypecode",
                            Operator = ConditionOperator.Equal,
                            Values = {entitytypeCodeOfTheEntity}
                        }
                    }
                }
            };

2 – Remove references to the entity from the form XML
For each form record returned in the above query, we can cast the formXML attribute as XML, and search for the particular node which references the item we want to remove. Once we have found the relevant node, we can then remove it from the formXml, and update the formXml on the entity and update it.

We will use an XPath expression to search the XML for a particular node. The XPath expression will depend on whether we are looking for an entity (a lookup field) or a section (containing an associated view).

                string xpath;                
                string searchString;
                if (!string.IsNullOrEmpty(sectionName))
                {
                    xpath = "//section[@name='" + sectionName + "']";
                    searchString = sectionName;                    
                }
                else if (!string.IsNullOrEmpty(attributeName))
                {
                    xpath = "//cell[control/@id='" + attributeName + "']";
                    searchString = attributeName;
                }

If the XPath expression returns a result, we can then remove the node and update the form:

                string formXml = form["formxml"].ToString();

                if (formXml.Contains(searchString))
                {
                    Console.WriteLine("Form contains " + searchString);

                    // Remove the section from the document
                    XmlDocument doc = new XmlDocument();
                    doc.LoadXml(formXml);

                    XmlNode root = doc.DocumentElement;
                    XmlNode node = root?.SelectSingleNode(xpath);

                    if (node == null)
                    {
                        xpath = "//tab[labels/label/@description='" + searchString + "']";
                        node = root?.SelectSingleNode(xpath);
                    }

                    if (node != null)
                    {
                        node.ParentNode?.RemoveChild(node);
                        Console.WriteLine("Node has been removed  ");
                    }
                    else
                    {
                        Console.WriteLine("Node not found");
                    }

                    form["formxml"] = doc.InnerXml;
                    service.Update(form);

3 – Publish the changes
Your changes to the form will not be permanent unless you publish them (which means you can practice the process in Dev until you are sure it is right).
You can publish changes to an entity using a method like this:

        public void PublishChangesToEntity(string entityName, IOrganizationService service)
        {
            Console.WriteLine("Publishing entity " + entityName);
            PublishXmlRequest request = new PublishXmlRequest
            {
                ParameterXml = @"<importexportxml>
                                       <entities>
                                          <entity>" + entityName + @"</entity>
                                       </entities>
                                       <nodes/>
                                       <securityroles/>
                                       <settings/>
                                       <workflows/>
                                    </importexportxml>"
            };

            try
            {
                var response = (PublishXmlResponse) service.Execute(request);
                Console.WriteLine("Changes to entity " + entityName + " published");
            }
            catch (Exception e)
            {
                // If entity not found, most likely has been deleted in previous run - not an error
                if (e.Message.Contains("was not found in the MetadataCache"))
                {
                    Console.WriteLine(e);
                }
                else
                {
                    Console.WriteLine(e);
                    throw;
                }
            }
        }

We will need to do the same if there are any views of Entity A which contain any attributes from Entity B – we will not be able to delete the relationship between the entities whilst these attributes are on the view. As before, we can query the fetchXml and the layoutXML of the view and update it. For example, we can use a fetchXml query like the one below to find any views based on a particular entity:

<fetch top='100' >
  <entity name='savedquery' >
    <all-attributes/>
    <filter type='and'>
       <condition attribute='returnedtypecode' operator='eq' value='{0}' />                  
    </filter>
  </entity>
</fetch></pre>
<pre>

and we can then modify the “layoutxml” and “fetchxml” attributes of the returned query definitions to remove any reference to the attributs which we want to remove.

With this technique it is easy to automate the process of removing sections or attributes from forms, or attributes from views, which will then enable you to programatically remove relationships between entities and the entities themselves.

Posted in CI, Dynamics CRM | Tagged | Leave a comment

Leave a Reply