General Tips on Dynamics CRM Plugin Development

On my blog, one of the most viewed posts is Dynamics CRM Plugin Development: Pre-Operation vs Post-Operation. This makes me realized that I need to create another blog post about plugin development, in general, to make you know what’s the dos and don’ts in plugin development (in a general way). The concepts of this blog post is a combination between my experience and taken from a code-reviewed document that Microsoft did on my company I currently working on.

Utilized PreImage and PostImage for Retrieving Main Entity

Microsoft suggests us to make use of PreImage and PostImage instead of retrieving using service.Retrieve(pluginExecutionContext.PrimaryEntityName, pluginExecutionContext.PrimaryEntityId, new ColumnSet(“attribute1”))

The explanation for it is simple. If you keep retrieving your main entity in the code, it will waste the resources and lead to performance issues.

Avoid ColumnSet(true) and NoLock = True

The common things that we do as a developer is we tend to retrieve more compare to the data that we need. The second one is if you retrieve inside the plugin, we tend to forget to set NoLock to false if the data that we want to retrieve is not important (dirty read). If you want to boost the performance try to retrieve the attributes that you only need + set the NoLock as true.

You can read about NoLock more:

Make Your Plugin Assembly + Step Simple

Microsoft recommended to avoid registering multiple assemblies / multiple plugin steps for the same entity (table). The rule is the same with Workflow, the more you have Workflows that are registered in the same Entity, the more it will be slowing down + complex to check if you have a bug.

These are my recommendations for creating plugin steps:

using Microsoft.Xrm.Sdk;
using System;
namespace Demo.Plugins
{
    public class PreCreateOperation : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            //Your business Logic
        }
    }
    public class PostCreateOperation : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            //Your business Logic
        }
    }
    public class PostCreateOperationAsync : IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            //Your business Logic
        }
    }
}

I never a fan of combining plugin messages and make a switch statement to differentiate Create, Update or Delete. I’ll make sure I differentiate in the plugin step to make clear what the plugin step for it.

Set Filtering Attributes on Update Message

Filtering Attributes have the same function with the Workflow Record fields change. It defines that your plugin will only be called if the system found the attributes that you defined being changed (in the Update Message). Because we filtering the attributes that can call the plugin, sure it will improve the execution time.

But for me, I’ll choose this approach to reducing multiple plugin steps (set Filtering Attributes to all attributes):

And here is the sample code of the plugin:

public void Execute(IServiceProvider serviceProvider)
{
    var context = (IPluginExecutionContext)
        serviceProvider.GetService(typeof(IPluginExecutionContext));
    
    var target = (Entity)context.InputParameters["Target"];
    if (target.Contains("totalamount") || target.Contains("totaldiscountamount"))
    {
        OnPriceChange(serviceProvider);
    }
    if (target.Contains("accountid"))
    {
        OnAccountChange(serviceProvider);
    }
}

Instead of relying on Filtering Attributes, I will check on the inputTarget.Contains(“attribute”) to determine if the business logic needs to be run or not.

The idea of the above approach is to make developers easier to expand the business logic without needed to change the plugin steps configuration every time the code is added. But in case you want more strict and get better performance, you can combine both ways.

Avoid Make Use of pluginExecutionContext.Depth

So many times I saw developers make use of pluginExecutionContext.Depth. The pluginExecutionContext.Depth is a data that you can track from the plugin to know the number of the depth of the current execution. Usually, they use pluginExecutionContext.Depth to avoid the infinite process error and that is okay. But if you want to analyze deeper, it is a sign that you have missing business logic to avoid this kind of error.

public void Execute(IServiceProvider serviceProvider)
{
    var context = (IPluginExecutionContext)
        serviceProvider.GetService(typeof(IPluginExecutionContext));
    if (context.Depth == 2) return;
    ValidateOrder(serviceProvider);
}

My recommendation for this is to make use of pluginExecutionContext.SharedVariables to giving some flag and return it. Here is a sample of the code before you call the service.Update method:

private void UpdateOrder(IServiceProvider serviceProvider)
{
    var context = (IPluginExecutionContext)
        serviceProvider.GetService(typeof(IPluginExecutionContext));
    var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
    var service = serviceFactory.CreateOrganizationService(context.UserId);
    var target = (Entity)context.InputParameters["Target"];
    context.SharedVariables["update-flag"] = true;
    var updateEntity = new Entity(target.LogicalName, target.Id);
    updateEntity["new_ordernumber"] = "RANDOM";
    updateEntity["new_qty"] = 2;
    updateEntity["new_total"] = new Money(2000);
    service.Update(updateEntity);
}

Then here is the code to check weather you need to avoid calling plugin again if you detect there is the flag that you pass:

public void Execute(IServiceProvider serviceProvider)
{
    var context = (IPluginExecutionContext)
        serviceProvider.GetService(typeof(IPluginExecutionContext));
    if (context.SharedVariables.ContainsKey("update-flag")) return;
    UpdateOrder(serviceProvider);
}

Because I just want to check whether the flag is exists in the SharedVariables or not, using pluginExecutionContext.SharedVariables.ContainsKey is valid in this scenario.

You can check some ideas of pluginExecutionContext.SharedVariables  in here.

Don’t Use The Same Object To Update!

This is a big no, no! If you ever retrieve one entity in your code and use it to update. You will instill a bad habit for you, your friends in the organization. Updating a record using retrieve results also will make your audit history growing up faster.

public void Execute(IServiceProvider serviceProvider)
{
    var context = (IPluginExecutionContext)
        serviceProvider.GetService(typeof(IPluginExecutionContext));
    var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
    var service = serviceFactory.CreateOrganizationService(context.UserId);
    var target = (Entity)context.InputParameters["Target"];
    var childRef = target.GetAttributeValue<EntityReference>("new_childid");
    var childData = service.Retrieve(childRef.LogicalName, childRef.Id, new ColumnSet("new_qty", "new_total", "new_name"));
    childData["new_description"] = "just testing delete";
    //Never do this! A big no!
    service.Update(childData);
}

Summary

All of the topics above are just small portions of the things that I can explain to you at the moment. Most of the issues that I always get and done (in my work) related to Dynamics CRM are about performance issues. And it leads to the answer:  how we design our plugin.

I have a very simple approach when designing the plugin. As you can guess, you will know the common paradigm that we need to apply when we create the plugins. Those paradigms are:

  • KISS (Keep It Simple Stupid)
  • SoC (Separation of Concern)
  • etc

And to achieve that, we can apply TDD (Test Driven Development) to validate those paradigms above. You can check the series of TDD on Dynamics CRM Plugin development here:

What you think?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.