Wednesday, December 21, 2011

Auto number solution for Dynamics CRM 2011

Today I am going to show you how to write an auto number plugin for CRM 2011.
Here are the aims of this plugin
  • First and foremost it should be able to generate auto numbers
  • Can be used for multiple entities
  • Can be ported to multiple organisations
  • Should be configurable from the interface
    • We should be able to change the starting number
    • We should be able to change the increment number
    • We should be able to change the prefix/suffix and add any separators
To generate the auto number we are first going to create an auto number entity. This entity will hold all our autonumbers as well as any formatting.
Auto Number Entity
As you can see from the screen shot the auto number entity contains the following fields:
Entity Name: this would hold the name of the entity where you want to apply the auto number to
Entify Autonumber Field: this is the field of the Entity that would show and hold the autonumber.
Prefix, Prefix Separator, Suffix and Suffix Separator are self explanatory.
Increment unit: It is the increment number for the auto number field.
Counter: It is the starting number for the auto number field and it will subsequently show the incremented number as updated in the plugin.
Number formatter: This is a formatter that will be applied to the number field (i.e. counter) so for example the counter is 544 and the formatter is 0000 that will end up being 0544.

Okay so now that we have our auto number entity set up, we need to write the plugin.

public class AutoNumberPlugin:IPlugin
    {
        public void Execute(IServiceProvider serviceProvider)
        {
            Microsoft.Xrm.Sdk.IPluginExecutionContext context = (Microsoft.Xrm.Sdk.IPluginExecutionContext)
                serviceProvider.GetService(typeof(Microsoft.Xrm.Sdk.IPluginExecutionContext));
            if (context.InputParameters.Contains("Target") &&
            context.InputParameters["Target"] is Entity)
            {
                Entity entity = (Entity)context.InputParameters["Target"];

                IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
                IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

                string fetchXml = @"<fetch mapping='logical'> 
                                            <entity name='agd_autonumber'><all-attributes/>
                                                <filter type=""and"">
                                                        <condition attribute=""agd_entityname"" operator=""eq"" value='" + entity.LogicalName + "'" + " /></filter></entity></fetch>";
                System.Threading.Mutex mtx = null;

                try
                {                
                 
                    string mutextName = string.Format("{0}{1}", entity.LogicalName, "Autonumber");
                    mtx = new System.Threading.Mutex(false, mutextName);

                    mtx.WaitOne();
                    //get hold on the mutex and only release it after update was done ot the agd_counter entity
                    // not sure if this will work in a multi server environment

                   EntityCollection result = service.RetrieveMultiple(new FetchExpression(fetchXml));
                    string nextIncrementNumber = string.Empty;
                    if (result.Entities.Count == 1)
                    {
                        //retrieve the counter
                        Entity autoNumber = result.Entities[0];
                        if (!autoNumber.Attributes.Contains("agd_counter"))
                            throw new InvalidPluginExecutionException("agd_counter must contain a value");
                        if (!autoNumber.Attributes.Contains("agd_incrementunit"))
                            throw new InvalidPluginExecutionException("agd_incrementunit must contain a value");

                        int counter = Int32.Parse(autoNumber.Attributes["agd_counter"].ToString());
                        int incrementUnit = Int32.Parse(autoNumber.Attributes["agd_incrementunit"].ToString());
                        string prefix = autoNumber.Attributes.Contains("agd_prefix") ? autoNumber.Attributes["agd_prefix"].ToString() : string.Empty;
                        string prefixSeparator = autoNumber.Attributes.Contains("agd_prefixseparator") ? autoNumber.Attributes["agd_prefixseparator"].ToString() : string.Empty;
                        string suffix = autoNumber.Attributes.Contains("agd_suffix") ? autoNumber.Attributes["agd_suffix"].ToString() : string.Empty;
                        string suffixseparator = autoNumber.Attributes.Contains("agd_suffixseparator") ? autoNumber.Attributes["agd_suffixseparator"].ToString() : string.Empty;
                        string numberFormatter = autoNumber.Attributes.Contains("agd_numberformatter") ? autoNumber.Attributes["agd_numberformatter"].ToString() : string.Empty;
                        string fieldToUpdate;
                        if (autoNumber.Attributes.Contains("agd_entityautonumberfield"))
                            fieldToUpdate = autoNumber.Attributes["agd_entityautonumberfield"].ToString();
                        else
                            throw new InvalidPluginExecutionException("agd_entityautonumberfield should not be emplty");

                        nextIncrementNumber = BuildAutoNumber(prefix, prefixSeparator,
                            suffix, suffixseparator, counter, incrementUnit, numberFormatter);

                        entity.Attributes[fieldToUpdate] = nextIncrementNumber;
                        service.Update(entity);
                        //increment the autonumber entity
                        //and update it to record the counter

                        autoNumber.Attributes["agd_counter"] = counter + incrementUnit;
                        service.Update(autoNumber);
                    }
                }
                catch (Exception ex)
                {
                    if (mtx != null)
                    {
                        mtx.ReleaseMutex();
                        mtx = null;
                    }

                    throw new InvalidPluginExecutionException("An error occured in Autonumber plugin", ex);

                }
                finally
                {
                    if (mtx != null)
                        mtx.ReleaseMutex();
                }





            }
        }

        private string BuildAutoNumber(string prefix, string prefixSeparator, string suffix, string suffixSeparator, int counter, int incrementUnit, string numberFormatter)
        {
            bool hasPrefix = false, hasSuffix = false;

            string returnNumber = string.Empty;

            if (!string.IsNullOrEmpty(prefix))
            {
                hasPrefix = true;
            }
            if (!string.IsNullOrEmpty(suffix))
            {
                hasSuffix = true;
            }
            counter = counter + incrementUnit;
            returnNumber = (hasPrefix ? prefix + prefixSeparator : "") + counter.ToString(numberFormatter) + (hasSuffix ? suffix + suffixSeparator : "");


            return returnNumber;


        }
Some notes about the code above, I've used fetch XML to get the fields from the auto number entity. You can use LINQ as well if you generated strongly typed clasess via CRMSvcUtil tool in the SDK. I am using late bound entities so that it is easier to change later on without the need to regenerate strongly typed classes.
I am also using a mutex, this is there to ensure that concurrent create requests cannot occur simultaneously, it's not the best solution but it works.

The above plugin code will generate the increment number, but before that can happen we have to register the plugin using the plugin registeration tool. We need to register the assembly and the create a step for the entity where we want to run this auto number plugin on. We have to register it on the post-create step for the "Create" event of  your custom entity. The registeration tool can be found in the SDK\tools directory.
Thats it! your auto number is ready to go. Notice that this plugin can service multiple entities and can be easily ported to any organisation, also it can be customized from the interface and prefixes and suffixes can be added / changed.

16 comments:

  1. hi,
    Thanks for the blog...
    I am getting the below error!!!


    Unhandled Exception: System.ServiceModel.FaultException`1[[Microsoft.Xrm.Sdk.OrganizationServiceFault, Microsoft.Xrm.Sdk, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]: An error occured in Autonumber pluginDetail:

    -2147220891


    OperationStatus
    0


    An error occured in Autonumber plugin
    2011-12-29T09:06:24.8066406Z



    [AutoNumberGeneration: AutoNumberGeneration.MyPlugin]
    [07411616-fc31-e111-ab54-00016c96a032: AutoNumberGeneration.MyPlugin: Create of new_agd_autonumber]






    Kindly Help

    ReplyDelete
  2. it is because you need to add a reference called System.ServiceModel... if you don't have it, then you need to install windows identity foundation

    ReplyDelete
  3. Is it possible to have two AutoNumbers on one entity? One when the entity is created and another when it is deactivated?

    For example,
    Case 1 opened: 11-001
    Case 2 opened: 11-002
    Case 2 closed: C12-001
    Case 1 closed: C12-002

    ReplyDelete
    Replies
    1. yes it is possible to have two auto numbers, however you will have to modify the plugin and register two steps for the plugin. one step at the create, one on the update. Also on the update step you will have to add code to check that the status is deactivated and then increment(add) the number to the entity.
      Lastly you will have to add another attribute to the autonumber entity to record whether the auto number will be executed against the update event or the create event. It can just be a boolean (yes/no) field.
      Hope this helps

      Delete
    2. And not to forget, in case of deactivate, change the entity BEFORE it goes to the server for update, PreUpdate!
      Otherwise you have to reactivate (make your code ignore this update action), change the autonumber field value (ignore this again , again) and deactivate as these three will be separate Web service calls.

      Delete
  4. Hello Atif,

    First of all, congratulations on the great stuff you provide in your blog. I gave this autonumber solution a try because it looks to be a must have in almost every CRM project, and gap in the core product.

    This solution occurs without any problem in an on-premisse deployment, But when registered in Sandbox mode it wont work, any ideas how can i make it work on Sandbox?

    cheers
    Mário

    ReplyDelete
    Replies
    1. clearly it can be resolved by giving up the Mutex... Online constrains can be harsh sometimes...

      Very good work thought. Keep up

      cheers
      Mário

      Delete
  5. Hi... i just followed your code but I keep getting the error "An error occured in Autonumber plugin" without any description after that when trying to update a standard entity and a standard required column. Basically i want to set a sequence on Opportunity entity name field. The reason being this column is used in the sharepoint integration and want to have a sequence rather than wanting the user to enter a subject text.

    ReplyDelete
    Replies
    1. I got it to work after updating the update entity step
      entity.Attributes.Add(fieldToUpdate, nextIncrementNumber);

      also the updated the suffix separator before the suffix in the BuildAutoNumber function

      Thanks for the post

      Delete
  6. hello atif i tried ur code but it showing an exception below can please help to solve this issue

    dude i created a autonumbering entity same as above and registered this plug-in on post-operation ,create message,and entity=mycustomentity

    Unhandled Exception: System.ServiceModel.FaultException`1[[Microsoft.Xrm.Sdk.OrganizationServiceFault, Microsoft.Xrm.Sdk, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]: An error occured in Autonumber pluginDetail:
    -2147220891


    OperationStatus
    0


    An error occured in Autonumber plugin
    2012-06-13T15:22:13.5694757Z


    [Autonumbering: Autonumbering.Class1]
    [8a498cc4-67b5-e111-be81-e4115bf3fe3f: Autonumbering.Class1: Create of new_testautonumbering]

    ReplyDelete
  7. hello atif its working fine but only problem is it showing an exception when am increment the value in the autonumbering that is autoNumber.Attributes["agd_counter"] = counter + incrementUnit;
    service.Update(autoNumber);

    ReplyDelete
  8. hello atif it showing an exception when am updating the autonumber entity that is
    autoNumber.Attributes["agd_counter"] = counter + incrementUnit;
    service.Update(autoNumber);
    the error occured in the autonumbering plug-in
    i registered the plug-in at create= message ,entity =mycustomentity,post-operation

    ReplyDelete
  9. Hello Atif,
    I have tried your amazing solution with the opportunity entity
    But it throws the "An error occured in Autonumber plugin" exception and I believe that the problem is with this code block:
    System.Threading.Mutex mtx = null;

    try
    {

    string mutextName = string.Format("{0}{1}", entity.LogicalName, "Autonumber");
    mtx = new System.Threading.Mutex(false, mutextName);

    mtx.WaitOne();

    Kindly advise..

    ReplyDelete
  10. Many thanks for this code. With a few tweaks I was up and running in under an hour. Thanks again for sharing.

    ReplyDelete
  11. Hi Ross

    Great to know that you got it running. Can you please let me know what changes you did before it finally took off.

    ReplyDelete
  12. You will also have to grant create, read and write privileges for your security role to the Auto Number custom entity. (Administration/Security Roles. Open security role, select custom entities tab)
    This should help with the above unhandled exception issues.

    ReplyDelete