Show ALL related activities in a subgrid

Microsoft Dynamics CRM offers great capabilities for activity entities, both out of the box and custom activities.
This post will describe a way to extend those capabilities even more, using a quite simple plugin.

During eXtremeCRM in Warsaw last week, a fellow CRMian and frequent user of FetchXML Builder approached me to ask if I knew a way to create “dynamic” queries with some contextual awareness. He had used unsupported methods to inject FetchXML to subgrids, to be able to show all activities related to current record, not just those where the record is related through the Regarding field. As I will show in this post, this can be accomplished with a simple plugin and a custom view.

Background

image
image
image

Activities handle e-mail, phonecalls, tasks, meetings, and even faxes and letters. Common custom activities are sms, transactions, and any other date-bound entity that involves participants of some kind. Participants can be contacts, accounts, leads, users, and the activities can be regarding eny entity with the “Activities” option selected. When looking at an associated view of a record to display associated activities, this can display activities where the current record is either recipient, sender, participant or regarding the activity. This is done with specialized views built into CRM, as this gives a useful overview of all activities concerning the party. The problem is when you want to have a similar view embedded on the form in a subgrid. In this case, you can only add activities related to the record through the Regarding lookup on the activities. This will of course only show a fraction of all activities that may be related to the contact, account, or any other entity form the subgrid is placed on.  

Solution – the customizations

image
image
image

To solve this, we need to modify the query that is executed by CRM. Instead of filtering activities based on the value in the Regarding field, we want to verify that the current record is any kind of “party” to the activity. First, create a custom view for the activities, with a signature that can be identified by our plugin so that it does not trigger when we don’t want it to. Open the “All Activities” view, and select Save As to create your custom view. Add a filter criteria to only show records where Activity does not contain data. Save the view. The view is now pretty unusable. It will never show any records as activity is required for the activitypointer pseudo entity.   Next, add a subgrid to the form where you want to show all activities, and select the new view as the default and only view. The customizations are now in place, and when we view any record with this form, nothing will show in the added subgrid.

Solution – the plugin

To get the query we want to be passed to CRM, we will intercept the RetrieveMultiple message in the execution pipeline before it goes to CRM. By analyzing the request we see that the query is formed like this, after conversion to FetchXML:

<fetch distinct='false' no-lock='true' mapping='logical' page='1' count='4' returntotalrecordcount='true' >
   <entity name='activitypointer' >
     <attribute name='subject' />
     ...
     <filter type='and' >
       <condition attribute='isregularactivity' operator='eq' value='1' />
       <condition attribute='activityid' operator='null' />
       <condition attribute='regardingobjectid' operator='eq' value='633929A2-F2E1-E511-8106-000D3A22EBB4' />
     </filter>
     <order attribute='scheduledend' descending='false' />
     <link-entity name='systemuser' to='owninguser' from='systemuserid' link-type='outer' alias='activitypointerowningusersystemusersystemuserid' >
       <attribute name='internalemailaddress' />
     </link-entity>
   </entity>
 </fetch> 

A bunch of included attributes have been excluded for readability
So, let’s create a plugin that triggers Pre RetrieveMultiple of activitypointer, and investigate if it has our “signature”.

      public void Execute(IServiceProvider serviceProvider)
         {
             ITracingService tracer = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
             IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
             if (context.MessageName != "RetrieveMultiple" || context.Stage != 20 || context.Mode != 0 ||                 !context.InputParameters.Contains("Query") || !(context.InputParameters["Query"] is QueryExpression))
             {
                 tracer.Trace("Not expected context");
                 return;
             }
             if (ReplaceRegardingCondition(query, tracer))
             {
                 context.InputParameters["Query"] = query;
             }
         } 

This code is pretty self-explaining. The interesting part however, comes in the ReplaceRegardingCondition method…

        private static bool ReplaceRegardingCondition(QueryExpression query, ITracingService tracer)
        {
            if (query.EntityName != "activitypointer" || query.Criteria == null || query.Criteria.Conditions == null || query.Criteria.Conditions.Count < 2)
            {
                tracer.Trace("Not expected query");
                return false;
            }

            ConditionExpression nullCondition = null;
            ConditionExpression regardingCondition = null;

            tracer.Trace("Checking criteria for expected conditions");
            foreach (ConditionExpression cond in query.Criteria.Conditions)
            {
                if (cond.AttributeName == "activityid" && cond.Operator == ConditionOperator.Null)
                {
                    tracer.Trace("Found triggering null condition");
                    nullCondition = cond;
                }
                else if (cond.AttributeName == "regardingobjectid" && cond.Operator == ConditionOperator.Equal && cond.Values.Count == 1 && cond.Values[0] is Guid)
                {
                    tracer.Trace("Found condition for regardingobjectid");
                    regardingCondition = cond;
                }
                else
                {
                    tracer.Trace($"Disregarding condition for {cond.AttributeName}");
                }
            }
            if (nullCondition == null || regardingCondition == null)
            {
                tracer.Trace("Missing expected null condition or regardingobjectid condition");
                return false;
            }
            var regardingId = (Guid)regardingCondition.Values[0];
            tracer.Trace($"Found regarding id: {regardingId}");

            tracer.Trace("Removing triggering conditions");
            query.Criteria.Conditions.Remove(nullCondition);
            query.Criteria.Conditions.Remove(regardingCondition);

            tracer.Trace("Adding link-entity and condition for activity party");
            var leActivityparty = query.AddLink("activityparty", "activityid", "activityid");
            leActivityparty.LinkCriteria.AddCondition("partyid", ConditionOperator.Equal, regardingId);
            return true;
        }
image

A bit of explanation:
Line 3-7: First sanity check of the query to verify that we might find our signature.
Line 13-29: Looping through the conditions to find our signature.
Line 15-19: Identifies the null-condition that was introduced in the view.
Line 20-24: Identifies the relation for the view, used to extract the record id.
Line 30-34: Verification of our signature.
Line 35: Extract the id of the parent record which is being displayed.
Line 39: Remove the null-condition used only to identify our signature.
Line 40: Remove the condition that the activities must have current record as Regarding.
Line 43: Create link-entity to activityparty, where the current record should be found.
Line 44: Add condition that the associated party shall be the current record.
As can be seen in the Execute method above, if the query is updated by our code, the updated query will be put back into the plugin execution context, and thus the updated query is what will be executed by CRM.
Finally, the plugin shall be registered in CRM.

Result

image


The subgrid on the form now contains all activities that are related to the current contact, in this case.
If you activate Plug-in Trace Log for all executions, you can now see logs with something like this in the message block:

Checking criteria for expected conditions
Disregarding condition for isregularactivity
Found triggering null condition
Found condition for regardingobjectid
Found regarding id: 633929a2-f2e1-e511-8106-000d3a22ebb4
Removing triggering conditions
Adding link-entity and condition for activity party

Resources

The complete source code for this plugin, including a bit more extra debugging information than shown here, can be downloaded here.
A CRM solution with the customized activity view, modified contact form, the plugin assembly and a plugin step can be downloaded here as managed version and unmanaged version.

Solution Segmentation for entity ribbons

The new feature for segmenting solutions in Microsoft Dynamics CRM is absolutely awesome.
It finally allows you to only include the parts you actually want your solution to change.

For us as an ISV this is a huge improvement. We can now deliver solutions that are completely “plug and play” and still leave a minimal footprint on the whole customer system.

RibbonDiffXml segmentation

There is however one thing that is still not possible to separate with enough granularity. To include the ribbon definition of an entity, you need to check the Include entity metadata checkbox. image Unfortunately this also includes the entity display name, plural name, and all other “entity wide” settings such as Notes, Connections, Mail merge etc. The result of this is that if we include the Contact entity just to be able to add a solution specific button to the command bar, we must also include the entity settings.

Problem

When a customer has developed their own solutions, or got solutions from other third parties, we quite often face the situation where they have changed the name of an entity, e.g. Contact is renamed to Person. Now importing our solution to add this button to the contact command bar, will also change the name of the contact entity back to default, or rather to whatever it is called in our solution.

Solution

This could be solved by allowing a separate option for the RibbonDiff part of the entity definition when selecting components for the solution.

Vote!

If you agree with this proposal, you can contribute by voting up this item on Microsoft Connect.

Read more

MVP Jukka Niiranen recently wrote an excellent article describing Solution Segmentation in Microsoft Dynamics CRM.

CRM 2011: Multiple Cascade Delete – part 2

In my previous post CRM 2011: Multiple Cascade Delete – part 1 I discussed the limitations in relationship behavior configuration for manual intersect entities, and proposed a solution with a plugin and a configuration entity.

In this post I will go into the details of the plugin and how it is registered.

I will not go into the basics of writing a plugin, there are tons of examples out there.
You need to be somewhat familiar with the event execution pipeline in Microsoft Dynamics CRM 2011 and the SDK classes used when developing plugins using late binding.

Objective

Consider the following entity model:

AccountContact2

Role records should be deleted if either the associated contact or account is deleted, but only one of the relations can be configured with cascade delete. Responsibility records should be deleted if associated Role is deleted. The goal is to accomplish this through a generic plugin that is configured using a dedicated CRM entity.

Plugin overview

The plugin will contain three main blocks

  1. Configuration cache being loaded on demand and cleared whenever a Cascade Delete rule is being changed.
  2. Pre Validation step to retrieve all child records that should be deleted prior to the parent record being deleted.
  3. Pre Operation step to perform the deletion of retrieved child records.

The reason for separating block 2 and 3 is described in more detail in the previous post.

Continue reading “CRM 2011: Multiple Cascade Delete – part 2”

CRM 2011: Multiple Cascade Delete – part 1

As I have recently mentioned, the possibilities of defining cascade deletes in Microsoft Dynamics CRM 2011 are quite limited. Only one parent entity can have the relationship behavior set to Cascade Delete. When you create a manual intersect entity to connect two or more other entities, this constraint is simply not acceptable for the end users.

Scenario

Consider this classic scenario: Instead of just associating contacts with a parent account, you want to be able to define a more dynamic model.AccountContactThis could be accomplished using Connections and Connection Roles, but that too has a number of pros and cons, which I will not go into in this article. When creating the relations to the Role entity, only one of them (i.e. either the relation to Account, Contact or Function) can be defined with cascade delete. What you would like here is to specify Cascade for both Account and Contact, and Remove Link for Function.

When using a manual intersect entity as in this example, the Role object will loose all meaning if either the associated Contact or the associated Account is deleted, thus the Role should of course be deleted in both cases.

To solve this, I will create a plugin which can be configured to perform the cascade behavior where it is not possible to do it by customizations only.

Relationship Behavior

First a few notes about the different types of relationship behavior during delete. The Restrict behavior verifies if there are any existing associating records before stage 20 (Pre Operation). So this behavior cannot be used, as we want to perform our configured plugin delete within the triggering transaction to ensure proper rollback behavior. The Cascade behavior can only be defined for one relationship, which in this case will be to the Contact entity. The Remove Link behavior will leave the child records in CRM, which is possible as the relationship attribute will be nulled by CRM between stage 10 and 20. Using this behavior alone would leave Roles defining e.g. that “Jonas has function Consultant at company null” when deleting accounts.

Objective and Configuration

A plugin shall delete children of a parent record that is being deleted. To specify which relationships that shall invoke this function, I use a configuration entity in CRM.CascadeDeleteDefinition3 It is also possible to pass the configuration as parameters to the plugin constructor, but then you have to enter the configuration in the step registrations, which is not very user friendly to the sysadmin. The operation shall be performed in stage 20 (Pre Operation) as it will then be within the transaction of the triggering delete, and the children will be deleted before the parent record is actually removed from the database. As the lookup attributes are nulled before stage 20 of the event execution pipeline, the plugin will retrieve a list of the children to delete in stage 10. This list is passed to the plugin triggered in stage 20 within the context’s SharedVariables. To improve performance, a cache of Cascade Delete configurations is maintained in the plugin class. If a configuration record is created, updated or deleted, the cache will be cleared.

In the next post I demonstrate and explain the code in the plugin, and also provide a complete solution for deploying multiple cascade delete in your Microsoft Dynamics CRM. Stay tuned!

Automatically Set Regarding on New Activities

Summary

Missing functionality in the MS CRM 2011 OOB functionality:

  • Activities created from menu File – New Activity on entity forms do not get the Regarding field populated.
  • When creating new activities from the associated view on the entity form, the Regarding field is mapped properly.

FileNewActivityIn this post, I will demonstrate a javascript example of a generic way to populate the Regarding field on activities, where the OOB CRM functionality fails to do this.TaskWORegarding

Objective

Whenever possible, the activity form shall to try to find which record that should be set as regarding. While doing this, also provide an opportunity to specify which entities that shall be allowed as regarding for each type of activity entity.

Method

  1. As the activity form is not opened with any parameters indicating “where it came from”, I investigate window.parent.opener to find information of its origin.
  2. Metadata requests are used to find additional information of the source, to be able to map between ObjectTypeCode and LogicalName, as well as to dynamically find which attribute on the source that is the primary attribute (i.e. the “name” attribute”).
  3. I perform a REST request to find the name of the source record, instead of e.g. trying to walk through the source form’s DOM to find information about it.

Code samples

Function to verify and populate the Regarding field:

Cinteros.Xrm.Activity = {
      _verifyRegarding: function (regardingEntities) {
        try {
            // First check if we have a valid mapped regardingobject
            var regardingObjectId = Xrm.Page.getAttribute("regardingobjectid").getValue();
            if (regardingObjectId && regardingObjectId[0].entityType) {
                // We have a regarding object through mapping, verify it is from an allowed entity
                if (!regardingEntities || !regardingEntities.length) {
                    return true;
                }
                for (var i = 0; i < regardingEntities.length; i++) {
                    if (regardingObjectId[0].entityType === regardingEntities[i]) {
                        return true;
                    }
                }
                return false;
            }
            // No regarding object was set - now examine opener to see where we came from
            if (window && window.parent && window.parent.opener && window.parent.opener.location && window.parent.opener.location.href) {
                var href = window.parent.opener.location.href;
                // Extract parent etc from its href
                var parentEtc = Cinteros.Xrm.SDK.getParameter(href, "etc") || Cinteros.Xrm.SDK.getParameter(href, "oType");
                // Get metadata for parent entitytype
                var regardingEntityMeta;
                var entityMetadataCollection;
                if (!regardingEntities || !regardingEntities.length) {
                    // No allowed entities specified, load metadata for all entities
                    entityMetadataCollection = Cinteros.Xrm.SDK.RetrieveAllEntities();
                }
                else {
                    // Load metadata only for allowed entities
                    entityMetadataCollection = [];
                    for (var i = 0; i < regardingEntities.length; i++) {
                        entityMetadataCollection.push(Cinteros.Xrm.SDK.RetrieveEntity(regardingEntities[i]));
                    }
                }
                // Get the metadata for correct parent entity, based on etc/otc
                for (var i = 0; i < entityMetadataCollection.length; i++) {
                    if (entityMetadataCollection[i].ObjectTypeCode == parentEtc) {
                        regardingEntityMeta = entityMetadataCollection[i];
                        break;
                    }
                }
                if (regardingEntityMeta && regardingEntityMeta.ObjectTypeCode == parentEtc) {
                    // Extract parent id from its href
                    var parentId = Cinteros.Xrm.SDK.getParameter(href, "id") || Cinteros.Xrm.SDK.getParameter(href, "oId");
                    if (parentId) {
                        parentId = unescape(parentId);
                        var attributeMeta = Cinteros.Xrm.SDK.RetrieveAttribute(regardingEntityMeta.LogicalName, regardingEntityMeta.PrimaryNameAttribute);
                        // Retrieve the regarding entity, to be able to find its primary name
                        var regardingObject = Cinteros.Xrm.REST.Retrieve(regardingEntityMeta.SchemaName, parentId, "?$select=" + attributeMeta.SchemaName);
                        if (regardingObject) {
                            // Found regarding record, create lookup object
                            var regardingLkp = [{ "id": parentId, "entityType": regardingEntityMeta.LogicalName, "name": regardingObject[attributeMeta.SchemaName]}];
                            Xrm.Page.getAttribute("regardingobjectid").setValue(regardingLkp);
                            return true;
                        }
                    }
                }
            }
            return false;
        }
        catch (e) {
            window.alert("Error in verifyRegarding:nn" + e.description);
        }
    }
}

Note: The javascript-functions in namespace Cinteros.Xrm are part of our internally developed tools, but the names should be quite self-explanatory.
REST functionality can be replaced by similar functionality in the CrmRestKit, see http://crmrestkit.codeplex.com/, or by other custom made code.
MetaData functionality can be replaced by functionality in the MS CRM SDK, see Sample: Retrieve Entity Metadata Using JScript.
Feel free to contact me if you have any questions.

Function to register for the formLoad event on each activity entity that shall have this functionality:

Cinteros.Xrm.Activity = {
    formLoad: function () {
        try {
            // Only do this when creating new activities
            if (Xrm.Page.ui.getFormType() === 1) {
                // For this example - four entities are allowed to be set as regarding
                var allowedEntities = ["contact", "lead", "opportunity", "jr_my_custom_entity"];
                if (this._verifyRegarding(allowedEntities) === false) {
                    window.alert("Activity must be created from a valid regarding record.n(" + allowedEntities.toString() + ")");
                    Xrm.Page.ui.close();
                }
            }
        }
        catch (e) {
            window.alert("Error during formLoad:nn" + e.description);
        }
    }
}

It is possible to call the _verifyRegarding function without any parameter, thus allowing any entity as regarding object. This will however read all entity metadata from the database, which typically takes a few seconds. So specifying the allowed set of regarding entity types is recommended.
Exclude the if-clause when calling the _verifyRegarding function to ignore it’s return value. Then the function will simply populate the regarding field when possible, without any verification that the field must be populated, or that it must be populated by a specific entity type.
Note that this solution uses window references and url parameters to interpret the caller. This is probably not supported according to MS CRM SDK, but it is not unsupported either, as it does not alter the DOM or use undocumented javascript methods, and it includes quite good error handling.