Sunday, March 11, 2012

Reason of type mismatch exception when working with SPWeb property bag

Some time ago I faced with unclear problem: when I tried to add value to the SPWeb property bag like this:

   1: CheckBox chk1 = ...;
   2: CheckBox chk2 = ...;
   3: SPWeb web = ...;
   4:  
   5: web.SetProperty("Foo", chk1.Checked);
   6: web.SetProperty("Bar", chk2.Checked);
   7:  
   8: web.Update();

SetProperty method is just wrapper over AllProperties collection which may be more familiar to developers:

   1: public void SetProperty(object key, object value)
   2: {
   3:     this.AllProperties[key] = value;
   4: }

As you can see in the code above boolean values are added to the property bag. Surprisingly this code caused type mismatch exception during web.Update() call:

Specified data type does not match the current data type of the property.
   at Microsoft.SharePoint.Utilities.SPUtility.UpdateArrayFromHashtable(Object& o, Hashtable ht)
   at Microsoft.SharePoint.SPWeb.Update()

What was even more interesting, is that only one boolean value caused this error (e.g. “Foo” on the example above), i.e. when I commented this single line, code became working having other boolean variables stored into the property bag.

I checked SPUtility.UpdateArrayFromHashtable() method in reflector. It is not the clearest method I saw in my experience. Let’s try to investigate what it actually does. According to its name it updates array of properties (2 dimensional) from Hashtable which stores local copies, i.e. properties which are not saved to content database yet. Lets check how it is called from SPWeb.Update() method:

   1: public void Update()
   2: {
   3:     ...
   4:     if (this.m_htProperties != null)
   5:     {
   6:         ...
   7:         SPUtility.UpdateArrayFromHashtable(ref this.m_PropertyArray, this.m_htProperties);
   8:         ...
   9:     }
  10:     ...
  11: }

Type mismatch exception is thrown from the following part of the UpdateArrayFromHashtable() method:

   1: internal static void UpdateArrayFromHashtable(ref object o, Hashtable ht)
   2: {
   3:     // ...
   4:     IDictionaryEnumerator enumerator = ht.GetEnumerator();
   5:     while (enumerator.MoveNext())
   6:     {
   7:         int num5;
   8:         // ...
   9:         if (hashtable.ContainsKey(str))
  10:         {
  11:             num5 = (int) objArray[2, (int) hashtable[str]];
  12:             hashtable.Remove(str);
  13:         }
  14:         else if ((enumerator.Value != null) && (enumerator.Value is string))
  15:         {
  16:             num5 = 1;
  17:         }
  18:         else
  19:         {
  20:             num5 = 0;
  21:         }
  22:         object obj3 = enumerator.Value;
  23:         if (obj3 != null)
  24:         {
  25:             Type type = obj3.GetType();
  26:             // ...
  27:  
  28:             if (((num5 > 0) && (num5 < ObjectType.Length)) &&
  29:                 (ObjectType[num5] != type))
  30:             {
  31:                 int num7;
  32:                 if (((type != typeof(string)) ||
  33:                     (ObjectType[num5] != typeof(int))) ||
  34:                     !int.TryParse(obj3.ToString(), out num7))
  35:                 {
  36:                     if ((type != typeof(int)) ||
  37:                         (ObjectType[num5] != typeof(string)))
  38:                     {
  39:                         throw new SPInvalidPropertyException(
  40: SPResource.GetString("InvalidPropertyType", new object[] { str }), str);
  41:                     }
  42:                     // ...
  43:                 }
  44:                 // ...
  45:             }
  46:     // ...
  47: }

ObjectType has Type[] type (i.e. array of types) which is initialized in static constructor of SPUtility class:

   1: Type[] typeArray = new Type[4];
   2: typeArray[1] = typeof(string);
   3: typeArray[2] = typeof(int);
   4: typeArray[3] = typeof(DateTime);
   5: ObjectType = typeArray;

As you can see “type” variable in the code above contains type of the current property (current loop step property) from Hashtable which as you remember contains list of local unsaved copies. I.e. in our case it contains bool type.

Another interesting variable is “num5”. It contains index in ObjectType array (i.e. values from interval [0..3]) for the property type. Important that if Hashtable contains property with the same name which already exists in the properties array, num5 will contain index for type of this existing property. Check lines 9-13 above:

   1: if (hashtable.ContainsKey(str))
   2: {
   3:     num5 = (int) objArray[2, (int) hashtable[str]];
   4:     hashtable.Remove(str);
   5: }

(hastable variable here is just converted array of existing properties). Let’s return to the code which throws exception. Lines 28-29 shows us that num5 contains value 1,2 or 3 (i.e. that previous type of property was string, int or DateTime) and that current type is not the same which was before update:

   1: if (((num5 > 0) && (num5 < ObjectType.Length)) &&
   2:     (ObjectType[num5] != type))
   3: {
   4:     ...
   5: }

But how property with the same name but different type came to the property bag? When I asked colleagues about it I found that previously for workaround of another problem this property was added to the property bag via PowerShell:

   1: $site =  new-Object Microsoft.SharePoint.SPSite($url)
   2: $web = $site.OpenWeb()
   3: $web.AllProperties["Foo"] = "True"
   4: $web.Update()
   5: $web.Dispose()
   6: $site.Dispose()

As you can see it was added using string. Code which reads this property may work both with strings and booleans (property bag returns instance of object type), that’s why it was not mentioned before. But when we tried to update it using boolean value it failed with type mismatch exception.

In order to fix it, just remove old property from the property bag:

   1: web.AllProperties.Remove("Foo");
   2: web.Update();

After that code which adds boolean values into property bag will work.

No comments:

Post a Comment