SPPersisted Object vs PropertyBags

on Friday, September 25, 2009

Hierarchical object store vs Property bag

We find, when building MOSS solutions, that we generally have a need for configuration information stores at four different levels:

  • Web application level
  • Site collection level
  • Site level
  • List level

There are lots of options when it comes to choosing configuration information stores, but at the web application level SharePoint offers two choices that are quite natural: the <AppSettings> section of the web application web.config file and the hierarchical object store.

Of these two options, the hierarchical object store is by far the lesser known of the two. To put it shortly, the hierarchical object store offers a framework that allows third party applications to store configuration information by creating a class that inherits from the SPPersistedObject class in the Microsoft.SharePoint.Administration namespace. You can find more information about it here: http://www.bluedoglimited.com/SharePointThoughts/ViewPost.aspx?ID=271.


You should also take a look at http://www.codeplex.com/features, which contains a feature called "Manage Hierarchical Object Store" that offers a user interface for managing web application configuration information that is ultimately stored in the hierarchical object store. This example would be quite easy to adjust so that it caters the management of configuration info at different levels as well.

If you look at the site and list level, besides the hierarchical object store, you have another option offered by SharePoint: the property bag. At the site level, it can be accessed via the Properties property of an instance of the SPWeb class. Instances of SPList classes don’t have a property bag associated to it. However, list items do have such a property bag. Therefore, using the property bag of the root folder of a list is a natural alternative if you want to store list level configuration information.

Since we were curious how the performance of the hierarchical object store and the property bag compare to each other, we’ve devised a simple performance test incorporated in a console application. The console application stores and retrieves config info at the list level and uses three different methods for storing the data:

  • Storage in a single property in the property bag using XML serialization
  • Storage in the hierarchical object store
  • Storage in multiple properties in the property bag

We’re sharing the results of this simple test with you in this article.

Creating a persistable object

First, we’re creating a class that contains 10 fields that need to be persisted in the hierarchical object store. In order to do so, we’re creating a class that inherits from the SPPersistedObject class. The class will contain two constructors; one that is required by the SPPersistedObject and an empty constructor that needs to be there for serialization purposes. Every field that needs to be persisted needs to be decorated with the [Persisted] attribute. The end result is the following class:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;


using Microsoft.SharePoint;

using Microsoft.SharePoint.Administration;


namespace HObjectStore

{

 public class PersistStore : SPPersistedObject

 {

  [Persisted]

  public int number1 = 1;

  [Persisted]


  public int number2 = 1;

  [Persisted]

  public int number3 = 1;

  [Persisted]

  public int number4 = 1;

  [Persisted]

  public int number5 = 1;

  [Persisted]

  public int number6 = 1;


  [Persisted]

  public int number7 = 1;

  [Persisted]

  public int number8 = 1;

  [Persisted]

  public int number9 = 1;

  [Persisted]

  public int number10 = 1;


#region ctor


  //Exists for serialization purposes.

  public PersistStore ()

  {

  }


  public PersistStore(string strName, SPPersistedObject objParent, Guid objGuid)

  : base(strName, objParent, objGuid)

  {

  }

#endregion


 }

}

This class is quite simple to build, and quite simple to use as well. You can create a new instance of our PersistStore class by passing a key, a parent that also inherits from SPPersistedObject (which seems to hold true for many classes in the Microsoft.SharePoint.Administration namespace, in this example we’ll use an SPWebApplication object) and a GUID. Please note that the GUID needs to be unique, you can’t reuse the same GUID and use a different key. So, instantiating a PersistStore object looks something like this (where objSite is an instance of an SPSite object):

PersistStore objTest2 = new PersistStore("MyKey", objSite.WebApplication, objList.ID);

If you’ve already persisted info, you need to retrieve it from the hierarchical object store via its key, like so:

PersistStore objTest2 = objSite.WebApplication.GetChild("MyKey");


If you want to persist a persistable object in the hierarchical object store, all you need to do is call the Update() method (defined in the SPPersistedObject class), like so:

objTest2.Update();

Finally, you can delete persisted info by calling the Delete() and Unprovision() methods, also defined in the SPPersistedObject class. The code for doing this is shown here:

objTest2.Delete();

objTest2.Unprovision();

As you’ve seen, interacting with the hierarchical object store isn’t hard to do. We’ll be revisiting this code later in our performance test.


XML Serializing info in a single property of the property bag

We’ll also be building a class that we’ll XML serialize and store in a property of the RootFolder property bag of a list. The class itself, which we’ll call SerializeClass, is very simple. It contains 10 fields that’ll be persisted:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;


namespace HObjectStore

{


 public class SerializeClass

 {

  public int number1 = 1;

  public int number2 = 1;

  public int number3 = 1;

  public int number4 = 1;

  public int number5 = 1;

  public int number6 = 1;

  public int number7 = 1;


  public int number8 = 1;

  public int number9 = 1;

  public int number10 = 1;

 }

}

We’ve found some code that’s able to handle the XML serialization at http://www.dotnetjohn.com/articles.aspx?articleid=173, which saves us the trouble of explaining it. We won’t list the code here, but will include it in our test code. For now, it suffices to know that this solution is more complex and requires more code. If you want specifics about this topic, please refer to the aforementioned article.

Storing info in multiple properties of the property bag


Storing info in multiple properties of the property bag is the easiest scenario of the three. Our test application wants to store list specific configuration information. Since instances of the SPList class don’t have a property bag, we’ll use the property bag of the rootfolder instead. The following code shows how to read a property that contains an integer from the root folder property bag:

SPFolder objFolder = objList.RootFolder;

if (objFolder.Properties.Contains("number9"))

{

 int9 = Convert.ToInt32(objFolder.Properties["number9"]);

 int9++;

}

The following code shows how to add a value in the root folder property bag:


objFolder.Properties["number10"] = int10;

objFolder.Update();

So, that’s that, and we’ll see this code again in the "Creating a performance test" section.

Creating a performance test

Our performance test executes tests for all three scenarios we’ve described. The first test does XML serialization, the second test uses the hierarchical object store and the third test uses multiple properties to store single values. If you’ve read the article so far, you should be able to understand the code. If not, just skip this section and look at the "Results" section.

using System;

using System.Collections.Generic;

using System.Linq;


using System.Text;

using System.IO;

using System.Xml;

using System.Xml.Serialization;

using System.Diagnostics;

using Microsoft.SharePoint;

using Microsoft.SharePoint.Administration;


namespace HObjectStore

{


 class Program

 {

  static void Main(string[] args)

  {

   try

   {

    Stopwatch objTimer1 = new Stopwatch();

    Stopwatch objTimer2 = new Stopwatch();

    Stopwatch objTimer3 = new Stopwatch();



    int intRuns = 20;

    string TEST_KEY = "TestAppSetting";

    Guid objParentId = new Guid();

    Guid objListId = new Guid();


    using (SPSite objSite = new SPSite("[your server]"))

    {

     SPWeb objWeb = objSite.OpenWeb();

     SPList objList = objWeb.Lists["Suggestions"];


     objTimer1.Start();


     for (int i = 0; i < intRuns; i++)

     {

      SerializeClass objTest1;


      string strXmlSerializedObject = null;

      if (objList.RootFolder.Properties.Contains(TEST_KEY))

      {

       strXmlSerializedObject = objList.RootFolder.Properties[TEST_KEY].ToString();

      }



      if (strXmlSerializedObject == null)

      {

       objTest1 = new SerializeClass();

      }

      else

      {

       objTest1 = (SerializeClass)DeserializeObject(strXmlSerializedObject);

      }


      //Change object


      objTest1.number1++;

      objTest1.number2++;

      objTest1.number3++;

      objTest1.number4++;

      objTest1.number5++;

      objTest1.number6++;

      objTest1.number7++;

      objTest1.number8++;

      objTest1.number9++;


      objTest1.number10++;


      //Serialize object.

      String XmlizedString = null;

      MemoryStream memoryStream = new MemoryStream();

      XmlSerializer xs = new XmlSerializer(typeof(SerializeClass));

      XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);

      xs.Serialize(xmlTextWriter, objTest1);

      memoryStream = (MemoryStream)xmlTextWriter.BaseStream;

      XmlizedString = UTF8ByteArrayToString(memoryStream.ToArray());



      // Save serialized object.

      objList.RootFolder.Properties[TEST_KEY] = XmlizedString;

      objList.RootFolder.Update();

     }
     objTimer1.Stop();


     objParentId = objSite.WebApplication.Id;

     objListId = objList.ID;


     objTimer2.Start();

     for (int i = 0; i < intRuns; i++)


     {

      PersistStore objTest2 = objSite.WebApplication.GetChild(TEST_KEY);


      if (objTest2 == null)

      {

       objTest2 = new PersistStore(TEST_KEY, objSite.WebApplication, objList.ID);

      }


      objTest2.number1++;

      objTest2.number2++;


      objTest2.number3++;

      objTest2.number4++;

      objTest2.number5++;

      objTest2.number6++;

      objTest2.number7++;

      objTest2.number8++;

      objTest2.number9++;

      objTest2.number10++;

      objTest2.Update();



      //Use the following code to delete info

      //objTest2.Delete();

      //objTest2.Unprovision();


     }

     objTimer2.Stop();


     SPFolder objFolder = objList.RootFolder;

     objTimer3.Start();

     for (int i = 0; i < intRuns; i++)


     {

      int int1 = 1;

      int int2 = 1;

      int int3 = 1;

      int int4 = 1;

      int int5 = 1;

      int int6 = 1;

      int int7 = 1;

      int int8 = 1;


      int int9 = 1;

      int int10 = 1;


      if (objFolder.Properties.Contains("number1"))

      {

       int1 = Convert.ToInt32(objFolder.Properties["number1"]);

       int1++;

      }

      objFolder.Properties["number1"] = int1;


      if (objFolder.Properties.Contains("number2"))


      {

       int2 = Convert.ToInt32(objFolder.Properties["number2"]);

       int2++;

      }

      objFolder.Properties["number1"] = int1;

      if (objFolder.Properties.Contains("number1"))

      {

       int1 = Convert.ToInt32(objFolder.Properties["number1"]);

       int1++;


      }

      objFolder.Properties["number2"] = int2;


      if (objFolder.Properties.Contains("number3"))

      {

       int3 = Convert.ToInt32(objFolder.Properties["number3"]);

       int3++;

      }

      objFolder.Properties["number3"] = int3;


      if (objFolder.Properties.Contains("number4"))


      {

       int4 = Convert.ToInt32(objFolder.Properties["number4"]);

       int4++;

      }

      objFolder.Properties["number4"] = int4;


      if (objFolder.Properties.Contains("number5"))

      {

       int5 = Convert.ToInt32(objFolder.Properties["number5"]);

       int5++;


      }

      objFolder.Properties["number5"] = int5;


      if (objFolder.Properties.Contains("number6"))

      {

       int6 = Convert.ToInt32(objFolder.Properties["number6"]);

       int6++;

      }

      objFolder.Properties["number6"] = int6;


      if (objFolder.Properties.Contains("number7"))


      {

       int7 = Convert.ToInt32(objFolder.Properties["number7"]);

       int7++;

      }

      objFolder.Properties["number7"] = int7;


      if (objFolder.Properties.Contains("number8"))

      {

       int8 = Convert.ToInt32(objFolder.Properties["number8"]);

       int8++;


      }

      objFolder.Properties["number8"] = int8;


      if (objFolder.Properties.Contains("number9"))

      {

       int9 = Convert.ToInt32(objFolder.Properties["number9"]);

       int9++;

      }

      objFolder.Properties["number10"] = int10;


      objFolder.Update();


     }

     objTimer3.Stop();


     Console.WriteLine(objTimer1.ElapsedMilliseconds);

     Console.WriteLine(objTimer2.ElapsedMilliseconds);

     Console.WriteLine(objTimer3.ElapsedMilliseconds);

    }

   }

   catch (Exception err)

   {


    Console.Write(err.Message);

   }

  }


  /// <summary>

  /// To convert a Byte Array of Unicode values (UTF-8 encoded) to a complete String.

  /// </summary>

  /// <param name="characters">Unicode Byte Array to be converted to String</param>


  /// <returns>String converted from Unicode Byte Array</returns>

  //taken from: http://www.dotnetjohn.com/articles.aspx?articleid=173

  private static String UTF8ByteArrayToString(Byte[] characters)

  {

   UTF8Encoding encoding = new UTF8Encoding();

   String constructedString = encoding.GetString(characters);

   return (constructedString);


  }


  /// <summary>

  /// Converts the String to UTF8 Byte array and is used in De serialization

  /// </summary>

  /// <param name="pXmlString"></param>

  /// <returns></returns>


  //taken from: http://www.dotnetjohn.com/articles.aspx?articleid=173

  private static Byte[] StringToUTF8ByteArray(String pXmlString)

  {

   UTF8Encoding encoding = new UTF8Encoding();

   Byte[] byteArray = encoding.GetBytes(pXmlString);

   return byteArray;

  }


  public static Object DeserializeObject(String pXmlizedString)

  {


   XmlSerializer xs = new XmlSerializer(typeof(SerializeClass));

   MemoryStream memoryStream = new MemoryStream(StringToUTF8ByteArray(pXmlizedString));

   XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);

   return xs.Deserialize(memoryStream);

  }

 }

}

In the next section (called "Results") we’ll discuss what we’ve found when executing this test harness.


Results

The results of our test runs were, at least to us, quite surprising. The results are shown in the next table. It contains the number of runs. For instance, we’re updating and saving 10 properties. If we repeat this process 5 times, the number of test runs is 5.


(runs no)\(scenario)
XML serialize
Hierarchical store
Multiple props

2 runs

1689 msec

302 msec

46 msec

2 runs

1695 msec

334 msec

66 msec

10 runs

1887 msec

1398 msec

123 msec

10 runs

2122 msec

983 msec

275 msec

50 runs

2267 msec

4828 msec

602 msec

50 runs

2314 msec

CRASH

-


As you can see, XML serialization isn’t that fast, but it doesn’t become much slower either. It’s pretty stable and in the end, doing 50 test runs, it surpasses the performance of the hierarchical object store. Storing values in multiple properties in the property bag of the root folder turns out to be very fast and it’s stable too. The hierarchical object store is, at first, a lot faster than XML serialization, but never as fast as the multiple properties scenario. This mechanism isn’t quite as stable as the other two, its test performance results tend to fluctuate more. Then, when doing 50 test runs, the hierarchical object store scenario crashes badly and stays crashed, resulting in the following error message:

"An update conflict has occurred, and you must re-try this action. The object PersistStore Name=TestAppSetting Parent=SPWebApplication Name=[name of web application] is being updated by [domain]\[username], in the HObjectStore.vshost process, on machine [server name]. View the tracing log for more information about the conflict."

Based on these results, we’re guessing you’re better of storing site and list configuration info in the property bag instead of in the hierarchical object store as it seems to offer better scalability and more robustness.

0 comments: