Plugin on Retrieve and Retrieve Multiple – How bad is it?

I have managed to be in the Dynamics CRM/365 world for over 7 years without having to write a single plugin on Retrieve and Retrieve Multiple. The recommendation that I give is to stay away from those. The reason is simple, it sounds horrible from a performance standpoint, and even people from Microsoft have recommended against it in many scenarios. Faced with an issue recently where we had to really consider it, I did some research and testing to try to measure the impact of such plugins on system performance. This article provides some background as to why we recommend against these types of plugins, and it also provides some of our finding after we tested for performance.

Why are Plugins on Retrieve Multiple scary?

When looking at the event execution pipeline for Dynamics CRM/365, we need to consider that there are a lot of steps involved as part of every CRM transactions. To do anything, we need to go through the CRM web service APIs which will start the chain of events in the pipeline (pre-validate, pre-event, core action/database access and then post-event).


This means that in general, it is a good practice to build everything for optimized performance to give your user base a good experience. It’s not like having a custom database where you can create store procedures easily, add triggers and so on, taking advantage of the SQL Server features and infrastructure.

Now, back to Retrieve and Retrieve Multiple plugins.

Retrieve Multiple: Think about it this way, you retrieve a list of 200 accounts, your plugin on Retrieve Multiple fires once and gives you the list of accounts being returned to the screen with all columns being retrieved. For each of these row, you do some type of operation. It doesn’t too sound good, does it?

Retrieve: You double click on an account from a list view. As the account columns are being retrieved to display the account form on the screen, you plugin on Retrieve fires and gives you the account object. At that point you can modify the content of the columns being retured as required before they are returned to the screen for the user. This really doesn’t sound too bad.

Some Findings on the impact on Performance

To provide some context into what we were trying to do, I wrote about multi-language lookup in a previous blog post. One of the solution that we have considered for one of our client is to use a plugin on Retrieve and Retrieve Multiple in order to change the value of the lookup primary fields in order to display a value in the user’s current language. The method we used is similar to what Aileen Gusni does here and almost identical to what Scott Durow does here.

We store a Region in a custom Entity. Accounts have a lookup that indicates its region.

Scenario 1:

The Region’s Primary Field contains a concatenation of the English and French region names with a relatively safe separator (we use “|”). When you load the list view of accounts, we have a plugin on Retrieve Multiple that looks at the columns being returned. If the Region column is returned, we retrieve the user’s language, we split the name of the region with the separator (the name is available in the entity reference) and we replace its value in the target object by the region name in the user’s language. The plugin on Retrieve does the same operation on the single role being retrieved.

The average execution time for the Retrieve Multiple plugin when loading 150 rows was 15.43 milliseconds so 0.01543 seconds.
The average execution time for the Retrieve plugin to load one rows was too little for the system to return a value (we got 0 milliseconds every time).

Scenario 2:

The Region entity has English Name and French Name attributes. When you load the list view of accounts, we have a plugin on retrieve multiple that looks at the columns being returned. If the Region column is returned, we retrieve the user’s language, we then retrieve the English or French name from the Region entity and we replace its value in the target object by the region name in the user’s language. The plugin on Retrieve does the same operation on the single role being retrieved.

The average execution time for the Retrieve Multiple plugin to load 150 rows was 1003.94 milliseconds so 1.00394 seconds.
The average execution time for the Retrieve plugin to load one rows was 16.02 milliseconds so 0.01602 seconds.

What should you read into this?

While these numbers don’t look too crazy at all, especially in the first scenario, there are a lot of factors to take into consideration that are not really showing here and that will vary in almost any scenario.

  • What is the infrastructure you are running on? The faster your severs and networks, the better the performance will be.
  • What you do in these plugins matters a great deal. You should avoid or limit the number of read/writes to the database during the execution of those plugins.
  • Our tests were made with a low level of users in the system, it is critical to scale up and see what these numbers look like at peak time of your system.

With all this said, I still recommend against it. Use with a great deal of caution! If/when possible, use calculated fields instead of writing plugins on these messages. This will also keep you away from limitation such as this one.

Hope this helps!

CRM Plugin Code Structure – By functionality or by event?

When we work on CRM plugins, there is a key decision at the beginning of the development cycle when it comes to structuring your plugin code. There are two main ways of writing your plugins :  by events or by functionality. In this post, I will describe the two approaches and discuss some of their pros and cons.

Plugins can be described as handlers for events fired by Microsoft Dynamics CRM (source). There can be multiple handlers for single events.  Let’s use an example for the purpose of this post.

For the event, we’ll use the creation of a case (pre operation). We will consider that when  the case is created, there are two actions that need to be performed : generate and assign a unique case number and validate that required fields have authorized values in them.

Plugin structured based on Events

For the plugin structure based on event, it means we’ll have a single plugin registered on the Case Pre create event, and inside the plugin class we’ll have two business logic blocks of code to execute (one to generate the unique number, the validate the required fields).

SNAGHTML4e81484     SNAGHTML4e74f7f

Plugin structured based on Functionality

In this case, we’ll have two plugin classes registered on the same event (again, one plugin for unique number generation and another to validate the required fields) – Sorry, no screen shot here but you get the idea Smile.

Pros and Cons

Functionality Based Plugins

  • Pros
    • Ease to see what plugins does what when looking at Plugin Registration tool (or similar tools) because of more descriptive feature name in plugin name
    • Ability to disable specific features for various scenario (e.g. disable system integration or auto calculation plugins during data migration)
    • Unit Tests can be written against a plugin class to test one functionality
    • Provides the ability to easily write generic plugins and register on multiple events by any power user
  • Cons
    • Large amount of plugin classes
    • Large amount of registered plugin steps; can become big and hard to read/maintain

Event Based Plugins

  • Pros
    • Plugin registration is straight forward and easy to maintain
    • Ease to easily the operations performed at each event when looking at the code
    • Plugin classes are standardized : always tied to an event and single point of entry for all developer to see actions executed on the event
    • Unit tests can be written against specific Business logic methods and/or classes (even independently from the plugin execution context depending on how they are written)
  • Cons
    • Must use alternate mechanism to disable specific features if needed (e.g. configuration data element)
    • Possible timeout if executing multiple large business logic blocs (mind you, if you have a timeout in a plugin – which happens after two minutes, you probably have a design issue)
    • General  dependency to having a developer around
      • Generic business logic code blocks can be written but a developer is required to insert the business logic on additional events
      • To view the all operations performed for an event, a developer needs to look at the code

So which model should you use?

I have used both models heavily on various occasions. For me personally, the decision on which one to use comes down to a project’s needs and a coding style preference. As an example, if you know you’ll have a lot of generic features and want to have reusable plugins, you are better off using the functionality based model. The same applies if you know there are processes that require to disable specific functionality on a regular basic. On the other hand, if you project is relatively small and includes simple business logic on a few events, the event based approach can be a good choice. Feel free to add feedback in the comment section if you see other pros and cons for each of these models or want to share experience in the area.

How to change a Plugin “Run in User’s Context” Programmatically

When you design Plugins for Microsoft Dynamics CRM, you sometime need to have them run as a different user in order to perform operations that a non admin user cannot normally do. This is usually done by using the Plugin Registration tool and setting the value of the “Run in User’s Context” drop down field to a specific value (see screenshot below).

Today I want to show a way to do this programmatically. Our design is fairly simple and the overall goal is to have the appropriate impersonation user being assigned to all required plugin steps automatically without manual processing post-solution import.

  • Create Plugins and steps with the Plugin Registration Tool as usual
  • The name of each plugin that requires to run as an admin user must contain a pre-defined text (in our case [Admin])
  • After each solution is imported to CRM, a plugin fires and look for all plugin steps where the name contains [Admin]
  • Updated the Run In User’s Context field with an admin user defined as per configuration

That’s easy enough. For step 4, where you store the configuration is up to you, it could be registry, XML web resource, custom entity etc.

Here is the code to do it:

Cheers

Managing CRM User Roles Programmatically

You often hear CRM users and admin complain about having to manage the system users’ roles too often. It can be a pain if you have a lot of movement in the company, for example people going from one business unit to another, high turnover rate or fast growing company.

I thought I would share a design idea for making user management a little bit easier for your organization. It consists of a few steps, and it requires some coding skills.

  • Define your business roles and match each of them to a set of CRM Security Roles.
Business Role CRM Security Roles
Sales Manager SalesPeron; Sales Manager
IT Manager System Customizer
  • On the “User” entity, create an Option Set field representing the business roles capture in step 1

Now the fun begins.

  • Create an XML file to map the business role option set values to a set to CRM Security Role names (see XML format in picture below)
  • Upload the XML file to CRM as a web resource
  • Switching over to the plugin side. You need to build a plugin that runs on update of a User record and that that does the following operations:
    • Read the “Business Role” field value on the User record
    • Retrieve the web resource configuration XML file based on its name or ID
    • Parse the XML to get the CRM roles corresponding to the business role selected
    • For each CRM Security Role found, assign it to the user
    • This is what my plugin code looks like:
public override void Execute(IExecutionContext context, IOrganizationService service, ITracingService tracingService)
{
 if (context.MessageName != "Update") return;

if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
 {
 Entity target = (Entity)context.InputParameters["Target"];
 if (target.Attributes.Contains("sa_userrole") && target.Attributes["sa_userrole"] != null)
 {
 SystemUser user = target.ToEntity<SystemUser>();

// Get Security Roles from Configuration
 WebResource wr = GetWebResourceByName(WebResourceName, service);
 string xmlContent = UnicodeEncoding.UTF8.GetString(Convert.FromBase64String(wr.Content));

XmlDocument config = new XmlDocument();
 config.LoadXml(xmlContent);

XmlNodeList crmRoles = config.SelectNodes("/SystemRoles/SystemRole[@ID=" + user.sa_UserRole.Value + "]/Role");
 foreach (XmlNode role in crmRoles)
 {
 Role crmRole = FindSecurityRole(user, role.InnerText, service);
 AssignSecurityRole(crmRole, user, service);
 }
 }
 }
}

/// <summary>
/// Retrieves a CRM Web resource by name
/// </summary>
private WebResource GetWebResourceByName(string webResourceName, IOrganizationService service)
{
 QueryByAttribute requestWebResource = new QueryByAttribute
 {
 EntityName = WebResource.EntityLogicalName,
 ColumnSet = new ColumnSet(true),
 };
 requestWebResource.Attributes.AddRange("name");
 requestWebResource.Values.AddRange(webResourceName);

EntityCollection webResourceCollection = service.RetrieveMultiple(requestWebResource);
 return (WebResource)webResourceCollection.Entities.FirstOrDefault();
}

/// <summary>
/// Find a Security Role by name and business unit
/// </summary>
private Role FindSecurityRole(SystemUser user, string roleName, IOrganizationService service)
{

if (user.BusinessUnitId == null || user.BusinessUnitId.Id == Guid.Empty)
 {
 SystemUser retrieveUser = service.Retrieve(user.LogicalName, user.Id, new ColumnSet("businessunitid")).ToEntity<SystemUser>();
 user.BusinessUnitId = retrieveUser.BusinessUnitId;
 }

// Retrieve a role from CRM.
 QueryExpression query = new QueryExpression
 {
 EntityName = Role.EntityLogicalName,
 ColumnSet = new ColumnSet("roleid"),
 Criteria = new FilterExpression
 {
 Conditions =
 {
 new ConditionExpression
 {
 AttributeName = "name",
 Operator = ConditionOperator.Equal,
 Values = {roleName}
 },

 new ConditionExpression
 {
 AttributeName = "businessunitid",
 Operator = ConditionOperator.Equal,
 Values = { user.BusinessUnitId.Id }
 }
 }
 }
 };

return service.RetrieveMultiple(query).Entities.Cast<Role>().FirstOrDefault();
}

/// <summary>
/// Assign Security Role to a CRM User
/// </summary>
private void AssignSecurityRole(Role securityRole, SystemUser user, IOrganizationService service)
{
 service.Associate("systemuser",
 user.Id,
 new Relationship("systemuserroles_association"),
 new EntityReferenceCollection() { securityRole.ToEntityReference() });
}

At this point, all you have to do is change that field on your users’ form and the plugin will handle assigning the security roles automatically. You may want to implement a way to handle the security roles assigned to users before the business role is changed. If you are planning to use this design or something similar, here are some key takeaways:

  • The same logic cannot be processed at the creation of the “User” record. That is because before being able to assign security roles to a user, the system needs additional information that gets created after the user record is created.
  • The plugin must run as an administrator or a user who has enough privileges to create and assign security roles to a user
  • The configuration is stored in a XML web resource but it could be stored anywhere (CRM entity, external database, flat file etc.)
  • Also note that Security Roles are copied to each business unit, so when looking a Security Role in CRM based on its name, it is important to search in the same business unit as the user’s
  • CRM does not allow to edit multiple user records at the same time

Have fun with this!