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.
SelectMany, Sorting and Grouping Objects
So here is the problem: I have a list of items that
var collection = new[] { new { Title = "One", References = "1;3" }, new { Title = "Two", References = "2;3" }, new { Title = "Three", References = "1;4" }, new { Title = "Four", References = "4"} };
The References fields of these object is some kind of category. What I want to do here is to have a list for each different reference (in this example: 1, 2, 3 and 4) containing all the items that are in the reference. Items will be duplicated if they are in more than one category.
To sum it up, the expected output would be: One, Three, Two, One, Two, Three, Four
After fooling around a bit, here is the query I came out with:
var query = from c in collection from d in c.References.Split(';') orderby d group c by d into groups select groups;
This does exactly what I want and produces the output I expected from the input data.
However, when I use Linq, I generally use extensions methods directly and not the pretty query syntax. This is mostly because I want to understand what happens behind the scene, and I have to admit that this query was quite a beast.
First, as there are two from clauses, there is a SelectMany somewhere. You probably know that SelectMany is a kind of the beast and that understanding it fully is quite a challenge compared to the other operators/extensions methods. Also, I thought that the GroupBy clause was going to be tough, as we groups c items by d which is in the other collection.
I couldn’t figure out by myself how to write that query using extension methods, so I fell back on the good old Reflector that gave me a straight answer:
var query = collection.SelectMany(delegate (<>f__AnonymousType0c) { return c.Values.Split(new char[] { ';' }); }, delegate (<>f__AnonymousType0 c, string d) { return new { c = c, d = d }; }).OrderBy(delegate (<>f__AnonymousType1<<>f__AnonymousType0 , string> <>h__TransparentIdentifier0) { return <>h__TransparentIdentifier0.d; }).GroupBy(delegate (<>f__AnonymousType1<<>f__AnonymousType0 , string> <>h__TransparentIdentifier0) { return <>h__TransparentIdentifier0.d; }, delegate (<>f__AnonymousType1<<>f__AnonymousType0 , string> <>h__TransparentIdentifier0) { return <>h__TransparentIdentifier0.c; }).Select(delegate (IGrouping <>f__AnonymousType0 > groups) { return groups; });
After reading that, it made much more sense. Here is what I came up with when writing it on my own:
var p = collection .SelectMany(c => c.References.Split(';'), (c, d) => new { c, d }) .OrderBy(t => t.d) .GroupBy(t => t.d, c => c.c);
Much more readable. The idea here is that the SelectMany clause outputs a sequence of anonymous types that contains the two kind of elements. This sequence is then sorted with the OrderBy, and finally fed trough a GroupBy that uses the d property as the grouping key and the c property as the project in the resulting collections. Not that difficult after all…
Here is another version that is probably a bit more clear:
var q = collection .SelectMany(c => c.References.Split(';'), (c, d) => new { Title = c.Title, Reference = d }) .GroupBy(c => c.Reference, c => c.Title) .OrderBy(g => g.Key);
Note that this is a simplified version of the original issue. The issue itself was to do this with some ListItems retrieved from SharePoint. Objects were a bit more complicated, but logic is the same.
Enumerable.Empty with null Coalescing Operation
Today I read Eric Lipert’s blog latest entry. It is about the semantic difference between null and empty. An easy example is with Collections. An empty Collection is not the same as an non-existing (ie. null) Collection.
However, as mentioned, how may times did you wish that foreach statement would work on an null Collection? How convenient would it be!
So far, what I was doing was:
foreach (var item in list ?? new List<SomeType>()) { //Do Stuff... }
But something nicer exists: Enumerable.Empty. Using that, the code becomes:
foreach (var item in list ?? Enumerable.Empty<SomeType>()) { //Do Stuff }
It’s actually longer, but it reads easier. The intent is quite obvious, I bet you can show this to someone who doesn’t know about the null coalescing operator and he would get what it does! Unfortunately I’m now working on Java and my colleagues would burn me alive if they knew I secretly pledged allegiance to .NET…
