Generating Unique IDs in Windows SharePoint Services 3.0

Switch View :
ScriptFree
Generating Unique IDs in Windows SharePoint Services 3.0

Summary:  Learn to create a unique identifier in Windows SharePoint Services 3.0 for all items contained within a site.

Visual How To

Applies to:  Windows SharePoint Services 3.0

Microsoft Corporation

March 2008

Overview

In many scenarios, it is useful to have a system-generated unique identifier for all items contained within a site. For example, you may wish to create a unique identifier for all records submitted to the Records Center. Regardless of which library that the record is stored, each record contained within the site has a unique identifier. SharePoint generates an ID for each item in a list, but these ID's are repeated in each list or library and are not unique across the site. Internally SharePoint creates a unique GUID for each item, but it is not exposed to the end user, and even if it were, GUID's are not very user-friendly.

Implementation of our unique ID includes a list for maintaining the value, and an event handler for retrieving and setting the value.

Code It

In this example we use a custom list named "Counter" containing one list item. The list item contains a value in the "Title" field which is used as the unique identifier. Using a SharePoint list automatically provides us with semaphore/locking behavior when retrieving and incrementing the value from the item in the Counter list. This is useful for handling race conditions which occur if multiple threads are using and incrementing the value at the same time.

Accessing and incrementing the unique identifier value is fairly straight-forward. You obtain reference to the list and item containing the identifier value, retrieve and increment the value, then update the item with the incremented value. The code is contained within a method named GetUniqueId and is called from the ItemAdding event handler when a document is added to a library. However, if two documents are added simultaneously, a race condition for the value occurs.

To handle this, the code is placed in a try-catch block. An exception is thrown if the value is dirty i.e. it was updated by another user between the time it was retrieved and updated. If the exception occurs, another attempt is made to retrieve and increment the value. In our example, the retry occurs five times before exiting.

C#
       private static int GetUniqueId(SPWeb web)
        {
            int returnValue = -1;
            int retryCounter = 0;
            int uniqueId = -1;

        ReTry:
            try
            {
                // Get the list and list item
                SPList list = web.Lists["Counter"];
                SPListItem item = list.Items[0];

                // Get the value for the unique id
                uniqueId = int.Parse(item["Title"].ToString());
                
                // Increment and update the value
                item["Title"] = uniqueId + 1;
                item.Update();
                
                // Set the value to return
                returnValue = uniqueId;
            }
            catch (Exception ex)
            {
                // Handle the exception by retrying 5 times
                retryCounter += 1;
                if (retryCounter <= 5)
                {
                    System.Threading.Thread.Sleep(2500);
                    goto ReTry;
                }
                else
                {
                    // Write the exception to the log
                }
            }
            return returnValue;
        }
Visual Basic
        Private Shared Function GetUniqueId(ByVal web As SPWeb) As Integer
            Dim returnValue As Integer = -1
            Dim reTryCounter As Integer = 0
            Dim uniqueId As Integer = -1

ReTry:
            Try
                ' Get the list and list item
                Dim list As SPList = web.Lists("Counter")
                Dim item As SPListItem = list.Items(0)

                ' Get the value for the unique id
                uniqueId = Integer.Parse(item("Title").ToString())

                ' Increment and update the value
                item("Title") = uniqueId + 1
                item.Update()

                ' Set the value to return
                returnValue = uniqueId
            Catch ex As Exception
                ' Handle the exception by retrying 5 times
                reTryCounter += 1
                If reTryCounter <= 5 Then
                    System.Threading.Thread.Sleep(2500)
                    GoTo ReTry
                Else
                    ' Write exception to the log
                End If
            End Try
            Return returnValue
     End Function

Once the value is obtained, it needs to be set on the item that is calling the event handler. In this example, all processing occurs in the synchronous ItemAdding event. After disabling event firing for the event handler, we obtain an SPWeb object and call our GetUniqueId method. It should be noted that, in this example, because we must get the SPWeb object we are adding an extra database round trip for every item added to the repository. In general this is not an ideal situation. The returned value is added to the AfterProperties collection as a name/value pair using the name of the field for the identifier. SharePoint will then automatically set the value when base.ItemAdding is called. Finally, event firing is re-enabled and base .ItemAdding is called.

C#
        public override void ItemAdding(SPItemEventProperties properties)
        {
            base.DisableEventFiring();
            try
            {
                using (SPWeb web = properties.OpenWeb())
                {
                    // Set the unique id value on the item
                    properties.AfterProperties["MyUniqueId"] = GetUniqueId(web);
                }
            }
            catch (Exception ex)
            { }
            finally
            {   base.EnableEventFiring();   }
            base.ItemAdding(properties);
        }
Visual Basic
        Public Overrides Sub ItemAdding(ByVal properties As SPItemEventProperties)
            MyBase.DisableEventFiring()
            Try
                Using web As SPWeb = properties.OpenWeb()
                    ' Set the unique id value on the item
                    properties. AfterProperties("MyUniqueId ") = GetUniqueId(web)
                End Using
            Finally
                MyBase.EnableEventFiring()
            End Try
            MyBase.ItemAdding(properties)
     End Sub

Read It

Our example uses a text field to hold the value. Other field types may be more appropriate based on the type of value you use. To ensure that the value is not arbitrarily modified by users, you should give consideration to security on the Counter list. The Counter list can also be hidden so it does not clutter navigation. Additionally, you can update the value and put the logic to retrieve into a separate class or in a location that controls access to the identifier value.

You can associate the event handler for retrieving and setting the identifier value with specific content types, such as Item or Document, to ensure that the value is generated for any item or document you add to the site. Also, the field used to hold and display the unique value should be read-only and sealed to prevent users from deleting it.

You can use a feature to deploy this functionality. When the feature is activated, you can use a callout code to provision the "Counter" list and ensure it is populated with a single item containing the starting value. You can associate the event handler for accessing and incrementing the counter value with a specific content type or registered on one or more libraries.

See It

Generating Unique IDs video splash screen

Watch the Video

Length: 6:39 | Size: 4.4 MB | Tyle: WMV

Explore It
Community Content

Sune Rievers
Better code example without goto

If you like me dislike gotos, this code might work better for you:

private static int GetUniqueId(SPWeb web)
        {
            return GetValue(web, 0);
        }

        private static int GetValue(SPWeb web, int retryCounter)
        {
            int uniqueId = -1;
            int returnValue = -1;
            try
            {
                // Get the list and list item
                SPList list = web.Lists["Counter"];
                SPListItem item = list.Items[0]; // Get the value for the unique id
                uniqueId = int.Parse(item["Title"].ToString());

                // Increment and update the value
                item["Title"] = uniqueId + 1;
                item.Update();

                // Set the value to return
                returnValue = uniqueId;
            }
            catch (Exception ex)
            {
                // Handle the exception by retrying 5 times
                retryCounter += 1;
                if (retryCounter <= 5)
                {
                    System.Threading.Thread.Sleep(2500);
                    return GetValue(web, retryCounter);
                }
                else
                {
                    // Write the exception to the log
                }
            }
            return returnValue;
        }


MikeBravo
The Property Bag analyzer trows in the dups
We have such a feature running here.

Basically the same codebase ut we use an external SQL data connection to autoincrement the numbers.
It works OK is you stick to content types and new created docs.

In practice we see a lot of File/Save As actions by office personel to create new documents instead of using templates.
I know this is bad behaviour, but it just works that way in practice.

If a document already has the Sharepoint applied document properties, the property bag analyzer tries to apply them to the colums.
The property bag analyzer can not be directed in one direction in the sense that only documents added will be left alone and let the feature apply a number. You can disable the property handler temporary as far as I discovered, but you cannot empty bogus properties available in documents

We solved it temporary by writing a word macro capture for the FileSaveAs() function and empty the server properties before it arrives in sharepoint.
But it should be handled on the sharepoint server IMHO. Same issues when using the barcode feature. All kinds of dups pop up.
We are running Sharepoint 2007 SP2 here, not sure if this works the same in 2010.

Moorsalone
Like George's response but would like a little more detail.
George

Hopefully you are still reading these posts all this time later. I was trying to add a unique identifier to each document and saw your response. My question is : does each one of these id numbers start with 1?  Is there any way to start the first id number with a higher number that actually looks like a reference number.

Thanks

George Gergues
Pros and Cons and A final Solution
Con s
The major problem with this technique is concurrency .
2 or more users with fast machines and network connections ( or many queued docuemnts) can cause this to deadlock.

Pro s
Easy to do , easy to deply and staple the feature to the site if you want and makes your life easy , as it is an event handler for on the onCreate or onNew.


Final Solution
I have been in this situation a few times, and here is the final working solution.

Why reinvent the wheel when SharePoint already has a Sequence generator that is Thread safe it is called (SharePoint List )

Simply on every Item you need a a new ID , create an Item in the new List , get the ID and give it to your Document List , announcent , or whatever Items you want Unique ID for.


Code is simple but not public.


Thanks and good luck

George Gergues
SharePoint Architect.

Jeffrey2793
Video is here
Sorry for any issues you may have experienced. The video is here:

http://wm.microsoft.com/ms/msdn/office/2007OfficeVisualHowTos/WSS30GenerateUniqueID.wmv

seschu01
Second on the Locking...

I have to wonder the same as the former poster....how do you ensure the same ID is not generated twice, or more without some locking mechanism?

// Get the value for the unique id
uniqueId = int.Parse(item["Title"].ToString());
// Increment and update the value
item["Title"] = uniqueId + 1;
item.Update();

I can definately see the issue where two users could generate two documents and winde up with the same ID thus causing some problems.



kadanous
Link to video does not work...
Also, is there a code download link available?

Alex Angas
Locking behaviour
Can this please be explained further as I'm not convinced. Surely there is a chance that code can run between when the data is retrieved:

     uniqueId = int.Parse(item["Title"].ToString());

and when Update is called?

     item.Update();

I would have thought you would need check in/check out enabled on the list to prevent locking.