This is not a question but instead a solution. I saw several questions asking about a solution for AutoNumbering a field in CRM. Plus I have used these forums a lot, so I figured I would post something for a change. Let me first appologize for any comment errors. I am being rushed and I want to get this out. Here is the solution I developed for AutoNumbering. It is a Plug-in for CRM 2011. It features:
- Auto-Number a field.
- The output field can be output as an integer or string.
- If the output format is a string, a format can be applied.
- The code locks itself when incrementing the AutoNumber so that there are not duplicate numbers.
- It uses a seperate entity to hold the last used AutoNumber value.
- The plugin will work on Create or Update as long as the AutoNumber field is in the InputParameters and is equal to zero, null or can be trimmed to an empty string.
Here is the disclaimer.
I, the author of this code am not responsible if it causes bad things to happen to your CRM. If you use and install any of the code contained in this post, you are agreeing that anything that happens IS YOUR OWN FAULT.
Here is the code.
using System;using System.Collections;using System.Text;using System.ServiceModel;using System.IO;using System.Diagnostics;using System.Xml;using Microsoft.Xrm.Sdk;using Microsoft.Xrm.Sdk.Query;// This plug-in is written so it can be used to AutoNumber multiple entities without having to rewrite and install another // plug-in. You just have to register a new step with a new configuration.// This plug-in uses uses a custom entity to store the next number in the number list. You need to store the name and Guid// for the custom entity in the configuration, which is then read into StorageEntityName, StorageEntityGuid. The // StorageEntityField is added to allow multiple AutoNumber processes to use the same instance of the same Entity to store// the last used number in the AutoNumber process.// The SharedLastNumber variable is used to try and reduce IO by storing the last number in it. If the value in this is zero, // then the value must be retrieved from the entity.// The plug-in is intended to be registered on the pre-validation or pre-operations stages for an entity.// Much of the code in this plug-in can be consolidated and shortened, but I left it in this format to assist debugging.namespace CRM.Plugin.AutoNumber {publicclass APRPluginAutoNumber : IPlugin {// Variable to hold the GUID of the specific entity that holds the last used number.privatestring StorageEntityGuid = "";// Variable to hold the Name of the specific entity that holds the last used number.privatestring StorageEntityName = "";// Variable to hold the Field Name of the StorageEntityName entity that holds the last used number.privatestring StorageEntityField = "";// This variable holds the name of the entity where we are going to store the next AutoNumber value.privatestring AutoNumberEntityName = "";// This variable holds the Field Name in the AutoNumberEntityName where we are going to store the next AutoNumber value.privatestring AutoNumberFieldName = "";// This tells the plug-in if the output is to be stored as a String or Int. If it is True, then it is a String // otherwise it is an Int.privatebool OutputAutoNumberAsString = false;// If the output is stored as a String, this is the format the string should be in.privatestring OutputAutoNumberFormat = "{0:0000000}";// Used to control access to the AutoNumber process, so we don't end up with duplicate numbers. This is in case // more than one person is inserting an entity that uses the AutoNumber process.private Object SharedLastNumberAccess = new Object();// Used to hold the most recent used AutoNumber in memory.privateint SharedLastNumber;// This plug uses the unsecure configuration section of the plug-in for configuring it. // The code is written into the constructor so that when the plug-in is instanced, it will// load it. This code is used to get the plug-in settings from the unsecure configuration section of the// plug-in registration. The unsecure configuration string should be an xml document with the // following form:// <?xml version="1.0" encoding="utf-8" ?>// <Config>// <StorageEntityName></StorageEntityName>// <StorageEntityGuid></StorageEntityGuid>// <StorageEntityField></StorageEntityField>// <AutoNumberEntityName></AutoNumberEntityName>// <AutoNumberFieldName></AutoNumberFieldName>// <OutputAutoNumberAsString></OutputAutoNumberAsString>// <OutputAutoNumberFormat></OutputAutoNumberFormat> // </Config>public APRPluginAutoNumber(string unsecure) {// Create an XML document to hold the configuration string. XmlDocument ConfigDoc = new XmlDocument();// Load the configuration into the XML document. ConfigDoc.LoadXml(unsecure);// Get the XML nodes that contains our configuration information. XmlNode StorageEntityNameNode = ConfigDoc.SelectSingleNode("//Config/StorageEntityName"); XmlNode StorageEntityGuidNode = ConfigDoc.SelectSingleNode("//Config/StorageEntityGuid"); XmlNode StorageEntityFieldNode = ConfigDoc.SelectSingleNode("//Config/StorageEntityField"); XmlNode AutoNumberEntityNameNode = ConfigDoc.SelectSingleNode("//Config/AutoNumberEntityName"); XmlNode AutoNumberFieldNameNode = ConfigDoc.SelectSingleNode("//Config/AutoNumberFieldName"); XmlNode OutputAutoNumberAsStringNode = ConfigDoc.SelectSingleNode("//Config/OutputAutoNumberAsString"); XmlNode OutputAutoNumberFormatNode = ConfigDoc.SelectSingleNode("//Config/OutputAutoNumberFormat");// Get our data and assign it to our variables. StorageEntityName = StorageEntityNameNode.InnerText; StorageEntityGuid = StorageEntityGuidNode.InnerText; StorageEntityField = StorageEntityFieldNode.InnerText; AutoNumberEntityName = AutoNumberEntityNameNode.InnerText; AutoNumberFieldName = AutoNumberFieldNameNode.InnerText;if ((OutputAutoNumberAsStringNode != null) && (OutputAutoNumberAsStringNode.InnerText.ToUpper() == "TRUE")) { OutputAutoNumberAsString = true; OutputAutoNumberFormat = OutputAutoNumberFormatNode.InnerText; }else { OutputAutoNumberAsString = false; OutputAutoNumberFormat = ""; } }// This method does the actual calculation of the next number and checking for skipping for reserved numbers, etc.publicvoid Execute(IServiceProvider serviceProvider) {// Obtain the execution context from the service provider. Microsoft.Xrm.Sdk.IPluginExecutionContext context = (Microsoft.Xrm.Sdk.IPluginExecutionContext) serviceProvider.GetService(typeof(Microsoft.Xrm.Sdk.IPluginExecutionContext));// If the context has an InputParameters target and it is an entity then we continue, otherwise skip out // of the plug-in.if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity) {// Obtain the target entity from the input parameters. Entity entity = (Entity)context.InputParameters["Target"];// Verify that the target entity represents out configured entity.// If not, this plug-in was not registered correctly.if (entity.LogicalName == AutoNumberEntityName) {// The InputParameters collection contains all the data passed in the message request.// We should only run this plug-in when the entity is first created or if (((context.MessageName.ToUpper() == "CREATE") && (entity.Attributes.Contains(AutoNumberFieldName) == false)) ||// during an update and only if the AutoNumber field is in the list of input parameters as being changed. ((context.MessageName.ToUpper() == "UPDATE") && (entity.Attributes.Contains(AutoNumberFieldName) == true) &&// and it is a string with a value of null or (((OutputAutoNumberAsString == true) && (entity.Attributes[AutoNumberFieldName] == null)) ||// it is a string and is empty or ((OutputAutoNumberAsString == true) && (entity.Attributes[AutoNumberFieldName].ToString() == "")) ||// it is not a string and has a value of null or ((OutputAutoNumberAsString != true) && (entity.Attributes[AutoNumberFieldName] == null)) ||// it is not a string and has a value of zero. ((OutputAutoNumberAsString != true) && (entity.Attributes[AutoNumberFieldName].ToString() == "0")) ) ) ) {// Obtain the organization service reference. IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory)); IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);#region AutoNumber Generation Code// Create a variable to hold our new number and set its default value to zero.int NewAutoNumber = -1;// This variable holds a reference to the entity used to store the last used number. Entity ReferenceEntity = null;// Lock our access object so no other threads can AutoNumber at the same time. This is done // to ensure that numbers are not duplicated.lock (SharedLastNumberAccess) {// We create a Guid for the particular instance of the storage entity that holds our value. Guid ReferenceEntityGuid = new Guid(StorageEntityGuid);// Retrieve our storage entity. ReferenceEntity = service.Retrieve(StorageEntityName, ReferenceEntityGuid, new ColumnSet(StorageEntityField));// Get the value off the specific field that holds our last used number. SharedLastNumber = int.Parse(ReferenceEntity[StorageEntityField].ToString());// We assign our SharedLastNumber to our NewAutoNumber before we do any processing of it. NewAutoNumber = SharedLastNumber;// Increment our new number NewAutoNumber = NewAutoNumber + 1;// We store our new number back into the storage entity. ReferenceEntity[StorageEntityField] = NewAutoNumber.ToString();// We save our storage entity with the new value. service.Update(ReferenceEntity);// We update the SharedLastNumber SharedLastNumber = NewAutoNumber; }#endregion#region AutoNumber Field Building Code// We check if the output is supposed to be a string.if (OutputAutoNumberAsString == true) {// We check to see if the Message is a Create or Update as it will change how // we apply the new AutoNumber. We store the AutoNumber according to the format in OutputAutoNumberFormatif (context.MessageName.ToUpper() == "CREATE") entity.Attributes.Add(AutoNumberFieldName, String.Format(OutputAutoNumberFormat, NewAutoNumber));else entity.Attributes[AutoNumberFieldName] = String.Format(OutputAutoNumberFormat, NewAutoNumber); }else {// Otherwise we store AutoNumber as an Int.if (context.MessageName.ToUpper() == "CREATE") entity.Attributes.Add(AutoNumberFieldName, NewAutoNumber);else entity.Attributes[AutoNumberFieldName] = NewAutoNumber; }#endregion } } } } } }
I encourage any improvements or features to be added to this code.
I hope this helps you.
Be Safe.
Tim Hays
PS. I removed code that I felt was probably unique to my situation and didn't involve the AutoNumber process anyway. I also added some checks to keep the code from erroring when processing the XML string in the constructor.