Friday, October 12, 2012

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.

1. Plugin configuration

I use an EntityCollection as a cache for all Cascade Delete rules defined in the configuration entity (see previous post).

public class CascadeDelete : IPlugin
{
    private EntityCollection _cascadeDeleteRules = null;

If the plugin is triggered by a change to any of these rules, it should be cleared to force a reload on next delete message from CRM.

if (context.PrimaryEntityName == "jr_cascade_delete" &&
    (context.MessageName == "Create" ||
     context.MessageName == "Update" || 
     context.MessageName == "Delete"))
{
    tracer.Trace("Cascade Delete rule changed, clearing cache");
    _cascadeDeleteRules = null;
    return;
}

When the plugin is triggered by an entity being deleted, the cache is loaded if it has not already been loaded.

if (_cascadeDeleteRules == null)
{
    QueryByAttribute query = new QueryByAttribute("jr_cascade_delete");
    query.AddAttributeValue("statecode", 0);
    query.ColumnSet = new ColumnSet("jr_parent_entity", "jr_child_entity", "jr_lookup_attribute");
    _cascadeDeleteRules = service.RetrieveMultiple(query);
}

2. Pre Validation – Retrieve children to delete

In this block, all rules in the cache will be examined to determine for each rule if it is applicable to the (parent) entity being deleted.

EntityCollection RecordsToDelete = new EntityCollection();
foreach (Entity rule in _cascadeDeleteRules.Entities)
{
    string parent = (string)rule["jr_parent_entity"];
    string child = (string)rule["jr_child_entity"];
    string attribute = (string)rule["jr_lookup_attribute"];
    tracer.Trace("Relation: {0} {1} {2}", parent, child, attribute);
    if (parent == context.PrimaryEntityName)
    {
        ...
    }
}

The collection RecordsToDelete is used to contain all child records, regardless of child entity type, that shall be deleted according to all applicable rules.

This list is populated from the child entities defined in the rules in this way.

QueryByAttribute qba = new QueryByAttribute(child);
qba.AddAttributeValue(attribute, context.PrimaryEntityId);
EntityCollection relatingEntities = service.RetrieveMultiple(qba);
tracer.Trace("Found {0} {1} to delete", relatingEntities.Entities.Count, child);
RecordsToDelete.Entities.AddRange(relatingEntities.Entities);

After iterating through all rules, the collection of child records to delete is added to the SharedVariables collection of the context.

if (RecordsToDelete.Entities.Count > 0)
{
    tracer.Trace("Adding total of {0} records to delete to SharedVariables", RecordsToDelete.Entities.Count);
    context.SharedVariables.Add(sharedVarName, RecordsToDelete);
}

The sharedVarName variable is defined based on entityname and id of the record being deleted.

3. Pre Operation – Delete children

This block is quite straightforward. If there is a collection of entities in the SharedVariables; delete them one by one.

if (context.ParentContext != null && context.ParentContext.SharedVariables != null && context.ParentContext.SharedVariables.Contains(sharedVarName))
{
    EntityCollection RecordsToDelete = (EntityCollection)context.ParentContext.SharedVariables[sharedVarName];
    tracer.Trace("Found {0} records to delete", RecordsToDelete.Entities.Count);
    foreach (Entity relatingEntity in RecordsToDelete.Entities)
    {
        tracer.Trace("Deleting {0} {1}", relatingEntity.LogicalName, relatingEntity.Id);
        try
        {
            service.Delete(childEntity.LogicalName, childEntity.Id);
        }
        catch (Exception ex)
        {
            tracer.Trace("Delete failed: {0}", ex.Message);
        }
    }
    context.ParentContext.SharedVariables.Remove(sharedVarName);
}

As described in the SharedVariables documentation, objects placed there in stage 10 must be accessed from the ParentContext in stage 20.

Note that these cascaded deletes will also trigger the Cascade Delete plugin and possibly a new chain of deletes, depending on how the Cascade Delete rules have been defined. Deletes being executed as a result of relationships defined with Parental behavior will also trigger the plugin. In the example model above, the parental relation between account and contact will trigger the role records to be deleted from "both directions".

The unconditional catch at the end of the code block above is just a simple safety precaution to handle cases where a child identified to be subject to a cascade delete rule has been deleted by other parental relationship behavior when this code segment is reached.

Download Solution

If you like the functionality of this plugin, but don't really feel like implementing your own version of it – you can download a complete managed solution HERE.

Disclaimer
It is possible to configure a combination of native parental relationship behavior and cascade delete rules in this solution which together may cause a recursive effect that can produce sql deadlocks. As all deletes are executed within the original delete transaction, this problem will not result in any inconsistent data.

Thursday, October 4, 2012

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!

Wednesday, October 3, 2012

CRM Plugins: Retrieve children during Delete

Tip of the day!

If you want to write a plugin that needs to read children of a record being deleted – this must be done in the Pre Validation stage.

Why is that?

For 1:N relations with Delete Behavior: Remove Link, the lookup to the parent being deleted is set to null somewhere between stage 10 (Pre Validation) and 20 (Pre Operation), but inside the transaction of the primary record deletion.

RemoveLink

So if trying to retrieve the children in any stage after Pre Validation you will not get any results, as they all have a not-yet-committed update transaction where the relation is nulled.

Why on earth should I care?

You might agree with me that the constraints regarding cascade behavior on relationships do not quite fulfill the needs that are quite common when creating manual N:N relations.

I will publish some tricks to generically cascade delete from several parents to a manual intersect entity in an article soon to come. Stay tuned!

Unfamiliar with native / manual N:N relations? See Richard Knudson's excellent article on this topic.

Monday, September 3, 2012

Monday, August 20, 2012

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

Thursday, August 9, 2012

Xrm.Utility methods in MS Dynamics CRM UR8

  • Have you ever used the unsupported javascript-function openObj to open a form in Microsoft Dynamics CRM 2011?
  • Have you ever cursed out loud over getting correct paths and parameters for URL Addressable Forms?
  • Have you ever implemented your own functionality to open a Microsoft Dynamics CRM 2011 webresource in a new window?

Stop that. Now.

At last, in UR8 Microsoft has included supported javascript-functions for those actions, providing a better user experience as well as nicer code than using the functionality of URL Addressable Forms and Views.

No new SDK version has been released yet, so you cannot read about it or find any examples there, it was just recently announced in The Microsoft Dynamics CRM Blog.

Basic description

There is a javascript library called Xrm.Utility which is available "everywhere" as long as you have a CRM context.

Xrm.Utility.openEntityForm(name, id, parameters)
Xrm.Utility.openWebResource(webResourceName, webResourceData, width, height)

Both functions return the window object that is created which allows you to e.g. move and resize the window.

The parameters parameter can be used to set default values when creating a new record and to specify which form to display.

One of the best things though – is that the openEntityForm function takes the LogicalName of the entity instead of forcing us to make a metadata request to get the ObjectTypeCode…!

Usage examples

Wednesday, August 8, 2012

Save & Publish Button for Forms and WebResources

While working with customizations and UI development in CRM 2011 – have you ever been looking for that "Save and Publish" button? Have you ever not been looking for it?

We all wonder why it is not there and there is a case on Microsoft Connect requesting it.
But relax, it is quite easy to get it in there right now.

Annoying facts:

SaveAndPublish32It seems to me that someone just forgot to implement this function. There is a button icon /CRMWEB/_imgs/ribbon/SaveAndPublish32.png (and corresponding 16 pix icon). The javascript function being called when clicking the existing Publish button in form customization is called SaveAndPublish(). It just does not do what the name indicates.

Solution:

To get a Save & Publish button:

  • Create a javascript webresource that first calls the Save function and then the Publish function
  • Create the button in the RibbonDiffXml section of customizations.xml, using existing icon files and calling the function created in step 1
  • Import the ribbon customizations

    SnP

    Javascript:

    Cinteros.Xrm.Customization = {
        SaveAndPublishForm: function () {
            try {
                SaveForm(false);
            }
            catch (e) { }
            try {
                SaveAndPublish();
            }
            catch (e) { }
        },
        SaveAndPublishWebResource: function () {
            try {
                SaveForm(false);
            }
            catch (e) { }
            try {
                PublishWebResource();
            }
            catch (e) { }
        }
    }
    RibbonDiffXml:

      <RibbonDiffXml>
        <CustomActions>
          <CustomAction Id="Cint.Mscrm.FormEditorTab.SaveButtons.Controls" Location="Mscrm.FormEditorTab.SaveButtons.Controls._children" Sequence="1">
            <CommandUIDefinition>
              <Button Command="Cint.Mscrm.FormEditorTab.SaveButtons.Controls.Button.SaveAndPublish" CommandType="General" Id="Cint.Mscrm.FormEditorTab.SaveButtons.Controls.Button.SaveAndPublish.Id" Image16by16="/_imgs/ribbon/SaveAndPublish16.png" Image32by32="/_imgs/ribbon/SaveAndPublish32.png" TemplateAlias="o1" LabelText="$LocLabels:Cint.Mscrm.FormEditorTab.SaveAndPublish.LabelText" ToolTipTitle="$LocLabels:Cint.Mscrm.FormEditorTab.SaveAndPublish.ToolTip" ToolTipDescription="$LocLabels:Cint.Mscrm.FormEditorTab.SaveAndPublish.ToolTipDescription" />
            </CommandUIDefinition>
          </CustomAction>
          <CustomAction Id="Cint.Mscrm.WebResourceEditTab.Save.Controls" Location="Mscrm.WebResourceEditTab.Save.Controls._children" Sequence="1">
            <CommandUIDefinition>
              <Button Command="Cint.Mscrm.WebResourceEditTab.Save.Controls.Button.SaveAndPublish" CommandType="General" Id="Cint.Mscrm.WebResourceEditTab.Save.Controls.Button.SaveAndPublish.Id" Image16by16="/_imgs/ribbon/SaveAndPublish16.png" Image32by32="/_imgs/ribbon/SaveAndPublish32.png" TemplateAlias="o1" LabelText="$LocLabels:Cint.Mscrm.FormEditorTab.SaveAndPublish.LabelText" ToolTipTitle="$LocLabels:Cint.Mscrm.FormEditorTab.SaveAndPublish.ToolTip" ToolTipDescription="$LocLabels:Cint.Mscrm.FormEditorTab.SaveAndPublish.ToolTipDescription" />
            </CommandUIDefinition>
          </CustomAction>
        </CustomActions>
        <Templates>
          <RibbonTemplates Id="Mscrm.Templates"></RibbonTemplates>
        </Templates>
        <CommandDefinitions>
          <CommandDefinition Id="Cint.Mscrm.FormEditorTab.SaveButtons.Controls.Button.SaveAndPublish">
            <EnableRules>
              <EnableRule Id="Mscrm.Enable.IsCustomizableManagedPropertyRule" />
            </EnableRules>
            <DisplayRules />
            <Actions>
              <JavaScriptFunction FunctionName="Cinteros.Xrm.Customization.SaveAndPublish" Library="$webresource:cint_/scripts/utils_support.js" />
            </Actions>
          </CommandDefinition>
          <CommandDefinition Id="Cint.Mscrm.WebResourceEditTab.Save.Controls.Button.SaveAndPublish">
            <EnableRules>
              <EnableRule Id="Mscrm.Enable.IsWebResourceCustomizableRule" />
            </EnableRules>
            <DisplayRules />
            <Actions>
              <JavaScriptFunction FunctionName="Cinteros.Xrm.Customization.SaveAndPublishWebResource" Library="$webresource:cint_/scripts/utils_support.js" />
            </Actions>
          </CommandDefinition>
        </CommandDefinitions>
        <RuleDefinitions>
          <TabDisplayRules />
          <DisplayRules />
          <EnableRules />
        </RuleDefinitions>
        <LocLabels>
          <LocLabel Id="Cint.Mscrm.FormEditorTab.SaveAndPublish.LabelText">
            <Titles>
              <Title languagecode="1033" description="Save and Publish" />
              <Title languagecode="1053" description="Spara och Publicera" />
            </Titles>
          </LocLabel>
          <LocLabel Id="Cint.Mscrm.FormEditorTab.SaveAndPublish.ToolTip">
            <Titles>
              <Title languagecode="1033" description="Save and Publish" />
              <Title languagecode="1053" description="Spara och Publicera" />
            </Titles>
          </LocLabel>
          <LocLabel Id="Cint.Mscrm.FormEditorTab.SaveAndPublish.ToolTipDescription">
            <Titles>
              <Title languagecode="1033" description="Saves and publishes the form" />
              <Title languagecode="1053" description="Sparar och publicerar formuläret" />
            </Titles>
          </LocLabel>
        </LocLabels>
      </RibbonDiffXml>
    

    Shortcut:

    If you don't want to do this manually, you can download a solution with just this content HERE.

  • Wednesday, July 25, 2012

    Execute Server-Side Code from Javascript

    Background:

    General business rules shall be implemented in server-side code to ensure its execution regardless of the origin of data changes; CRM UI, workflow, integration or any other application consuming the CRM web services.

    But on some occasions it would be handy to be able to execute this code from JavaScript to improve the user's experience of the application.

    Scenario:

    When creating or updating an Account, there is a plugin registered Pre Validation Create/Update to verify that the given VAT number is in a correct format. If not, the plugin throws an exception instructing the user to enter a correct VAT number.

    VATincorrect

    Another plugin is registered Pre Operation Create/Update to look up city/state/county from given zip-code to ensure correct data for the account. This function consumes an external service from a third party accessible as a web service.

    Account Cinteros zip

    Challenge

    To improve user experience, the customer requires immediate verification of the VAT no and lookup of geographic information for the zip-code.

    Wednesday, July 11, 2012

    Adding Duplicates in N:N-Relations

    The possibility to use many-to-many relations in Microsoft Dynamics CRM 2011 is very handy for various scenarios.
    However, it's implementation is not very forgiving when you try to add a relation which already exists.
     
    There is no way to configure the relationship or functionality of the "Add existing..." ribbon button to prevent this "ugly" message - so I decided to create a plugin which smooths things out, so that adding a relation which already exists only makes sure the relationship is there, but does not complain if it already was.
     
    Scenario
    We have a N:N-relation between Account and Product, to indicate which products are interesting to which accounts.
     
    Clicking the Add Existing Product button brings up the lookup dialog for products.
    When I now want to add a number of products, I do not want to have recall or check which products were already added. I just want to select all products (we can really sell anything to this company ;) click OK and be done with it.
     
    Solution
    We can create a plugin before main operation that verifies if the added relation already exists. But if it exists, we cannot prevent the relation from being created in any other way than throwing an exception, which will then result in a similar "ugly" message to the user, except it will contain the text we throw.

    Wednesday, July 4, 2012

    The Welcome Post

    There must be a Welcome Post.
    So this is it.

    Welcome to my blog about development on the Microsoft Dynamics CRM platform!

    The blog will focus on plugin development in C# and UI development in javascript, but in the words of mama Gump - Ya never know what'ya gonna get.