Plugin Best Practices in CRM 4.0

When it comes to writing plugins, there are three driving factors. 

·     Quality - If you don't follow best practices you will find defects in production.  The defects are usually challenging to troubleshoot. 

·     Performance - You are working with web services that can only update/insert/delete one record at time.  You have database records with 50+ columns that are retrieved over web services.  It isn't surprising that if you don't develop your software optimally, you will have overall system slowness. 

·     Maintainability - You are working with dynamic entities that contain a bag of generic properties.  You tend to see a great deal of casting to different CRM types.  It doesn't take very long before your data access code becomes cluttered with business logic.  Your code can start looking like spaghetti very quickly. 

I wanted to lay out some best practices that help honor these driving factors.

new AllColumns() == BAD && new AllColumns() != GOOD

Never return all columns from the web service.  Never!  Even a basic entity contains 20+ columns.  This takes a long time to make its way from the database and into your plugin.  Only return the fields that you need a fresh copy of.  If you will commonly return a set of columns, keep a member-level variable with a string array. 
 

Note:  One thing to watch out for when using a ColumnSet....  You may return a dynamic entity and check to see if the property bag exists in a field.  If it doesn't exist then it is assumed to be null in the database.  If you forgot to specify it in the ColumnSet you may get a false negative.

Code with ColumnSet Specified
 

 

Only Update Fields you Are Changing

Only send the fields you are changing to the database.  For example, let’s say you are doing a retrieve multiple, returning back 20 columns.  Then you update one CrmBoolean field and call service.Update.  It will send all 20 columns across the network.  This hurts performance.  It can also cause inadvertent results.  Even though you didn't change 19 of the fields, it will trigger a change on all 20 columns.  If you have a workflow or plugin that looks for these fields to change, it can cause infinite loops.  In the very least, you will have some extra records.  This can happen if you have an out-of-the-box workflow that looks for an "EmailToCustomer" CrmBoolean being updated to true.  If you sent this field to the database, it will fire.  Now you have duplicate emails being sent even though you didn’t actually change the field.

You have a couple of options to ensure that you only send what is changing to the database. 

1)  Keep a separate copy of things you want to update.  I have a helper function that returns a dynamic entity what only its key field.  You can add properties to the property bag and send them to the web service.  Don't just update the DynamicEntity that was returned from the web service.

internal static DynamicEntity GetBlankEntity(string entityName, string primaryKeyAttribute, Guid id)
{
    DynamicEntity entity = new DynamicEntity(entityName);
            entity.Properties[primaryKeyAttribute] = new Key(id);
    return entity;
}

DynamicEntity entity = CRMUtilities.GetBlankEntity("new_job", "new_jobid", id);
entity.Properties["new_jobcompleted"] = new CrmBoolean(true);
service.Update(entity);

2)  You can build it into your ORM, so it keeps a separate copy of a dynamic entity with changes.  More on this later...

 

Encapsulate the Extraction of Properties

One of the easiest ways to create a bug is to extract properties manually using casting.  Check out this code. 

Lookup custLookup = entity.Properties["customerid"] as Lookup;
Guid custId = custLookup.Value;

It looks like solid code right?  Well, you will actually get a null reference exception here.  The customerid field is actually a Customer property.  It can be an account, contact, or lead so they have a separate object for it.

As with the previous section, you have two options. 

1)  You can write a method to handle returning all Guids.  It will return a nullable Guid for Lookups, Owner, Customer, and Key.

Helper Method
public static Guid? GetGuid(DynamicEntity entity, string propertyName)
{
    if (entity.Properties.Contains(propertyName))
    {
        Lookup lookup = entity[propertyName] as Lookup;
        if (lookup != null)
        {
            if (lookup.IsNull || lookup.IsNullSpecified)
                return null;
            else
                return lookup.Value;
        }
        else
        {
            Key key = entity[propertyName] as Key;
            if (key != null)
            {
                return key.Value;
            }
            else
            {
                Owner owner = entity[propertyName] as Owner;
                if (owner != null)
                {
                    return owner.Value;
                }
                else
                {
                    Customer cust = entity[propertyName] as Customer;
                    if (cust != null)
                    {
                        return cust.Value;
                    }
                }
            }
        }

    }
    return null;

} 

  2)  You can build your ORM to extract the properties in their respective types.


Use an ORM (Object Relational Mapping)

You can help increase the quality and maintainability of your code by implementing an ORM.  I started building out my ORM by working off of the code that comes with David Yack (MVP)'s book CRM as a Rapid Development Platform.  It was amazing to see that code that would eliminate 70% of my bugs.  I thought, "I might actually start liking this CRM thing!"

Here are the keys to a CRM ORM:

·     Most importantly, you have an object that maps to each DynamicEntity.  Each property in the entity maps to a field in the object. They are exposed as nullable .Net Types.  You now treat your customerid field as a nullable guild.  You know that behind the scenes there was a CRM Customer object, but you don't have to deal with the inherent bugs.  This is done through code generation.  The properties are generated by using the metadata service.  You have helper methods that retrieve the nullable types from the values from the properties and store data in the properties.  You just deal with the plain old C# types.

Sample of Generated Class
public int? owneridtype
{
  get
  {
      if (this.Entity == null)
          return null;

      if (CTCDEPropHelper.PropertyExists(this.Entity, "owneridtype"))
          return CTCDEPropHelper.GetNumberValue(this.Entity, "owneridtype");
      else
          return null;
  }
  set
  {
      if (this.Entity == null)
          return;

      bool hasChanges = CTCDEPropHelper.AddNumberProperty(this.Entity, "owneridtype", value);
      if (!this.IsLoading && hasChanges)
      CTCDEPropHelper.AddNumberProperty(this.EntityChanges, "owneridtype", value);
  }
}

public string owneridname
{
  get
  {
      if (this.Entity == null)
          return "";

      if (CTCDEPropHelper.PropertyExists(this.Entity, "ownerid"))
          return CTCDEPropHelper.GetLookupPrimaryText(this.Entity, "ownerid");
      else
          return "";
  }
}

·     The objects keep track of fields that change, so you only send fields that change to the database.  The objects actually store two DynamicEntities.  One is the version that was returned from the web service.  The second contains only the Key field along with any changes (for updating).  The great part about this is that you can check to see if anything changed before calling an update.

Base Object Code
public virtual void Save(ICrmService service)
{
    if (this.IsDirty)
    {
                if (this.IsNew())
             service.Create(this.EntityChanges);
                else
            service.Update(EntityChanges);
    }
}

·      It allows you to follow the separation of concerns principal by keeping each layer of code in its own place.

 Sample Project Folder Structure

 

o  Keep your data access methods in their own folder, away from the actual plugin class.  You will still need to deal with the lengthy web service retrieve multiple request objects, so let’s keep that code away from the actual plugin.

o  I keep the generated objects their own folder as well. 

o  Lastly, I create a partial class of the generated objects and I keep them in a different folder.  That way you can re-generate the objects whenever you add fields to CRM, and you don't have to worry about losing your business logic code.

Partial Class with Same Class Name as Generated Objects
public partial class inetopportunity
{
   // Insert business logic code here
}


Use Pre-Event Plugins When Possible

Pre-event plugins occur before the changes are persisted to the database.  The advantage to using a pre-event plugin, particularly for Create and Update, is that you don't have to update the database twice.  You can update the fields that are being updated or pass new fields into the web service save process.  This isn't ideal for more complex scenarios, but if you are doing something simple such as separating a birth date fields into the day/month/year, pre-event plugins work great!  One of my favorites:  http://blogs.msdn.com/crm/archive/2009/02/27/creating-a-birthday-contact-list.aspx


Consider Using Asynchronous Plugins or Custom Workflows for Longer Running Processes

Synchronous plugins run while the user is sitting there waiting for the save to complete.  Your users' time is precious, so it is very important to pay attentions to the other tips I mentioned in this post.  If you followed all of them and you still have a process that runs for more than a couple seconds, consider running your code asynchronously.  This can be done using an asynchronous plugin or as a custom workflow.  For more information, stay tuned for next week's blog post titled, "An Argument for Asynchronous Plugins."

 -Andrew


Posted 01-25-2010 5:56 AM by Andrew Zimmer
Inetium, LLC. Site Information