(This is a follow-up to the previous post: Propagating Content Type Changes in MOSS 2007)
So far, I have propagated a few Content Type fields in a live production environment of ~65,000 webs using this method. Mind you, this does not mean that I have propagated a content type to 65,000 lists or items, just that the environment is large.
The whole game here revolves around Field Links (see the SPFieldLink class on MSDN). And to recap, the goal here is to be able to maintain your Content Types in in a Solution (WSP) feature. MOSS does not support updating content types "declaratively via XML" (i.e. through a solution package).
Here's what MOSS will do: In most cases, MOSS will add new fields to the root / site collection content type (make sure you do an RDAD deployment - Retract / Delete / Add / Deploy). What this actually means (and this is important) - is that MOSS will add the field and create a SPFieldLink which links the field to the root content type. This is why when you create a new list item based on the content type, that it will show up, but there are no field links automatically created for existing items.
So here's the work we need to do to propagate the field:
- If working with a large site collection (more than 1,000 webs), determine the usage count of the content type you're updating. This will give you an approximate idea of how long this is going to take to propagate (more on this below).
- Break the SPFieldLink at the root level, so that the field is no longer linked to the content type, and update the content type.
- Add the SPFieldLink to the content type, but this time, call .Update(true) on the content type, so that SharePoint will propagate the changes to all inherited items (including lists items).
Let's step through these...
1. Determine Usage Count - This determines where the content type is actually used, and therefore, the # of updates that will take place. A content type usage is defined as the # of items that actually use / inherit the content type.
This is simple - if you already have a SPWeb object called "web" and the Content Type name string called "contentTypeName", just use:
int contentTypeUsages = Microsoft.SharePoint.SPContentTypeUsage.GetUsages(web.ContentTypes[contentTypeName]).Count;
In testing so far, here are my performance findings as to how long it will take to propagate. Bear in mind that for smaller usage counts there is still some overhead time in establishing a context, retrieving web, etc. However, the results are not very linear, and I couldn't tell you why. This will just give you a very general and vague idea to set your expectations.
- 1 usage - 0.66 seconds per web (Total Time: 28 seconds)
- 6 usages - 0.01 seconds per web (Total Time: 19 seconds)
- 1,876 usages - 0.64 seconds per web (Total Time: 1192 seconds / 19 min, 52 sec)
- 13,138 usages - 0.53 seconds per web (Total Time: 1003 seconds / 16 min, 43 sec)
- 15,031 usages - 0.66 seconds per web (Total Time: 1234 seconds / 20 min, 34 sec)
2. Break the SPFieldLink at the root level - If you already see the field when you go to Site Collection Settings > Content Types, then you will need to break the SPFieldLink. Find the field link by looking in the SPContentType.FieldLinks collection (in the below example, this is the selectedFieldLink object, and selectedSiteColl is the SPSite site collection object).
SPFieldCollection fieldColl = selectedSiteColl.RootWeb.Fields;
3. Propagate the field - Example below - In this case I have created a FieldLinkInstance class that just holds some properties like the Site Collection, Content Type name, etc, but you should be able to see the general idea here. The most critical thing is the last line where you call c.Update(true).
public void PropagateFieldToSiteCollection(FieldLinkInstance fieldLink)
SPContentTypeCollection contTypes = fieldLink.SiteCollection.RootWeb.ContentTypes;
SPFieldCollection fieldColl = fieldLink.SiteCollection.RootWeb.Fields;
SPContentType c = contTypes[fieldLink.FieldLink.ContentType.ContentTypeName];
SPField newField = new SPField(fieldColl, fieldLink.FieldLink.Field.FieldType, fieldLink.FieldLink.Field.Name);
newField.Title = fieldLink.FieldLink.Field.DisplayName;
newField.StaticName = fieldLink.FieldLink.Field.StaticName;
newField.PushChangesToLists = true;
string strNewColumn = fieldColl.Add(newField);
SPField targetField = fieldColl.GetFieldByInternalName(strNewColumn);
SPFieldLink oFieldLink = new SPFieldLink(targetField);
Hopefully this will give you an idea and a little code to propagate content types using this method. I have created a class library that actually parses WSP solution packages to auto-detect unpropagated content types between a Solution and live MOSS farm and propagate them, but can't share the source code for that at the moment. In general, you can extract the WSP package as a CAB, then parse the XML files to extract the Content Types.