Copy a Field’s Value to another Field in SharePoint

If one of your field data type needs to be changed, you will run into issues when deploying the new version of your application, as SharePoint can’t translate from one data type to another. For example, if you change a field type from “Single Line of Text” to “Notes” in Fields.xml, everything will go wrong.

The clean way to do this is to create a new field, the one that will be used from now on, and copy the value of the old field to the new field, while applying a transformation if necessary.

For this, we wrote a generic method that will go trough the entire site and perform the changes on the specified fields.

public static void CopyFieldValues(SPSite site,
    String copyFromFieldInternalName,
    String copyToFieldInternalName,
    Func<Object, Object> transformation)
{
    foreach (SPWeb web in site.AllWebs)
    {
        //Get all the ListItems in every list of the web that has the 2 fields
        var items = (from l in web.Lists.Cast<SPList>()
                     where l.Fields.ContainsField(copyFromFieldInternalName)
                         && l.Fields.ContainsField(copyToFieldInternalName)
                     from i in l.Items.Cast<SPListItem>()
                     select i).ToList();

        //For each listitem in this web, migrate the content of the old one to the new one
        //To do this, we use the tranformation Func that was given as a parameter
        foreach (SPListItem item in items)
        {
            item[copyToFieldInternalName] = transformation(item[copyFromFieldInternalName]);

            //Do system update to avoid modifying the item's version
            item.SystemUpdate(false);
        }
    }
}

So, now that we have a generic method to copy the content of a field into another, we can “specialize” it for various uses.

One example is migrating a Single Line of Text type field to a Note type field. It is very easy because there is no transformation needed:

public static void CopyFieldValuesFromSingleLineOfTextToNote(SPSite site,
    String copyFromFieldInternalName,
    String copyToFieldInternalName)
{
    //No need to transform the data in this case
    SharePointFieldsHelper.CopyFieldValues(site, copyFromFieldInternalName, copyToFieldInternalName, oldFieldValue => oldFieldValue);
}

More on IsSPBuiltInField…

Some follow up on my previous post

I had to work with this a bit more today, so I wrapped it up a bit to make it more convenient to work with:

public static class SPFieldExtension
{
    static IDictionary<Guid, bool> PublishingBuildInFields { get; set; }

    static SPFieldExtension()
    {
        Type type = typeof(FieldId);

        var propInfoArray = type.GetProperties(BindingFlags.Static | BindingFlags.Public);

        SPFieldExtension.PublishingBuildInFields = propInfoArray
            .Select(prop => (Guid) prop.GetValue(null, null))
            .ToDictionary(g => g, g => true);
    }

    /// <summary>
    /// Check if the given field is a build in field of SharePoint (WSS and MOSS)
    /// </summary>
    /// <param name="guid"></param>
    public static bool IsSPBuiltInField(Guid guid)
    {
        return SPBuiltInFieldId.Contains(guid)
            || SPFieldExtension.PublishingBuildInFields.ContainsKey(guid);
    }

    /// <summary>
    /// Check if the given field is a build in field of SharePoint (WSS and MOSS)
    /// </summary>
    /// <param name="field"></param>
    public static bool IsSPBuiltInField(this SPField field)
    {
        return SPFieldExtension.IsSPBuiltInField(field.Id);
    }
}

So, I created a static class to hold my two methods. One is the same a the one in the previous post, taking a Guid and returning a Boolean. The second one is an extension method for SPField that simply call the first method. It’s more continent to use it this way.

Is a given Field one of SharePoint’s Built-In Field?

We struggle with this issue on my current project. The issue itself is rather trivial: we have a field (actually, the Guid of a field) and we want to know if it is one of SharePoint’s built-in field or if it is a custom field from our application.

Now, as our application runs on MOSS 2007, there are two kind of built-in fields: those of WSS 3.0 and those of MOSS 2007. For WSS 3.0 fields, there is a class named SPBuiltInFieldId that has all the built-in fields’ guids as public fields and the class even has a static method Contains that allows to quickly find out if the field is built-in from WSS 3.0 or not.

For MOSS 2007 fields, there is the Microsoft.SharePoint.Publishing.FieldId class that has all the built-in fields’ Guids as public properties, but unfortunately does not have a “Contains” method.

So how are we going to find out? Obviously, we are not going to compare against all the properties of the FieldId class… No, let’s do this the clean way: make use reflection to build up a Dictionnary with all the Guids then we’ll use that table to check if our field is in it or not!

static IDictionary<Guid, bool> PublishingBuildInFields { get; set; }

static Program()
{
    Type type = typeof(Microsoft.SharePoint.Publishing.FieldId);
 
    var propsInfoArray = type.GetProperties(BindingFlags.Static | BindingFlags.Public);

    PublishingBuildInFields = propsInfoArray
        .Select(prop => (Guid)prop.GetValue(null, null))
        .ToDictionary(g => g, g => true);
}
 
bool IsSPBuiltInField(SPField field)
{
    return SPBuiltInFieldId.Contains(field.Id)
        || PublishingBuildInFields.ContainsKey(field.Id);
}

Now, one interesting detail is that ToDictionary uses immediate execution, so we are sure to execute reflection calls only once, during the static constructor’s execution. This is important, because if we just built a sequence with Select, every time we could access that sequence the reflection code would execute, which is not a good idea in most cases.

Update: follow up here.

Reading an Int32 from a DataRow coming from SharePoint

In my previous post, I described how to convert a String to an Int32 making sure that Convert.ToInt32 works all the time for positive integers.

Now, there is still an issue with that method: when the integer is negative. In that case, adding a 0 in front of it does not work and makes the conversion throw a FormatException, as the underlying parse chokes on the “0-”.

Now, the obvious ultimate solution to that problem is simply to write an extension method for DataRow to try to parse the value as an Int32:

internal static int FieldAsInt32(this DataRow dataRow, String columnName, int defaultValue)
{
    var fieldValue = dataRow.Field<String>(columnName);

    int value;

    if (Int32.TryParse(fieldValue, out value))
    {
        return value;
    }
    else
    {
        return defaultValue;
    }

}

internal static int FieldAsInt32(this DataRow dataRow, String columnName)
{
    return dataRow.FieldAsInt32(columnName, 0);
}

I added an overload that takes the default value, i.e. the value that is to be returned if the parse fails. The default for that is 0. This might not be useful in all cases, but in our case this field is used to order elements, so if it can be parsed, it has to return a very high value to make sure it is the latest in the ordered sequence.

SharePoint, SPWeb Objects and Dispose()

If you worked a bit with SharePoint, you surely know how important it is to dispose of SPSite objects, SPWeb objects and such.

However, these objects are sometimes retrieved by using another object’s property. A very common one is SPContext.Current that has properties to return the current SPSite and SPWeb.

Needless to say, calling dispose on an object that you got via another object’s property seems utterly rude. I mean, you are basically disposing something that was “borrowed” from another object. When I see code doing this, I get very suspicious.

Here is a common example:

using (SPWeb web = SPContext.Current.Web)
{
    //Do something with web
}

Using reflector, here is what SPContext.Web does:

public SPWeb Web
{
    get
    {
        if (this.m_web == null)
        {
            this.m_web = SPControl.GetContextWeb(this.m_context);
        }
        return this.m_web;
    }
}

As we can see, this returns a private field of type SPWeb. Calling Dispose() on that (via the using statement) is clearly not very nice to our beloved SPContext. This kind of coding is known to bring unexpected errors. In fact, FxCop will raise a warning if it is set to check for SharePoint Best Practices Rules.

If this is a general rule, there are of course exceptions, I believe due to some inconsistencies in the API. Here is one of them:

SPWeb web = SPContext.Current.Web;

foreach (SPWeb subWeb in web.Webs)
{
    //Do something with subWeb

    subWeb.Dispose();
}

In this particular case, we iterate over a collection of SPWeb objects trough the SPWeb.Webs property. As said before, at first sight, this looks bad.

Again, using reflector, this is what the Webs property does:

public SPWebCollection Webs
{
    get
    {
        if (this.m_Webs == null)
        {
            this.m_Webs = new SPWebCollection(new SPWebCollectionProvider(this), this.m_guidId);
        }
        return this.m_Webs;
    }
}

It is very similar to the previous code, so it feels very unnatural to call Dispose() on all object that are held in that collection.

However, this is the way to go, otherwise FxCop will raise a warning saying that SPWeb objects were not disposed of. In my opinion, this shouldn’t be a property, but a method called GetWebs() or something similar, as explained in Framework Design Guidelines.

Related links:

Next Page →