I’ve been doing a lot of work with Linq to SharePoint over the past month, creating content for my new SharePoint 2010 development book. You know it’s a strange thing writing a technical book, when you first start out you wonder how you’ll fill the pages. As you get into it though you very quickly find that there’s a lot more stuff you’d like to add but don’t have the space.
I like to take things to bits completely before I start wiring about them. It’s always good to know where the limits are before you think about scenarios that can illustrate a feature - Saves you a lot of rework if you find that things don’t work quite as you’d hoped! Of course the problem with that approach it you can easily spend a lot of time trying to push the boundaries and at the end of the day what you discover isn’t relevant to the vast majority of folks who’re happy to use things as they are.
Anyways, since I’ll no doubt forget what I’ve discovered in a weeks time, I'm going to post my boundary explorations and ideas here. Maybe some day I'll have time to use them for something interesting!
Linq to SharePoint is a great thing for developers. Not just because we no longer need to use CAML or because we can use it to performs joins across lists. For me the main reason for using Linq is that it eliminates the need to use indexed properties. Indexed properties are evil as I'm sure you’ll know if you’ve done any extensive SharePoint development.
We don’t want to use:
properties.ListItem["MyBooleanField"] = true;
Life is so much sweeter if we can write:
item.MyBooleanField = true;
Back in the day, using MOSS 2007, we had to spend a bit of time creating a set of business objects to do this. It was time well spent in the long run but time nonetheless. Now with Linq, this work is done for us by SPMetal – it analyses the content types on a site and creates entity classes for every content type/ list combination (well almost every content type, we’ll deal with that in another post). So now when we want to deal with items in a list we can do it in a type-safe way:
using (EventHandlerDemoDataContext ctx = new EventHandlerDemoDataContext(properties.WebUrl))
{
var anItem = (from i in ctx.SampleLIst
where i.MyColumn1 == "A strongly types value"
select i).First();
anItem.MyColumn1 = "Some otehr value";
ctx.SubmitChanges();
}
Now that’s progress in my opinion.
Here’s the thing though…
What happens if you’re writing an event receiver? You end up with code that looks a bit like this:
public override void ItemAdded(SPItemEventProperties properties)
{
base.ItemAdded(properties);
if (properties.ListItem["MyField"].ToString() == "my value")
{
DoSomething(properties.Web);
}
properties.ListItem["AnotherField"] = 1;
properties.ListItem["MyBooleanField"] = true;
properties.ListItem.Update();
}
And we’re back to square one. Of course we could create a DataContext and query the list or library in question to retrieve an instance of the object that the event’s being executed on. But my conscience tells me that’s just wrong.
A better way to deal with this problem is to devise a way to convert an SPListItem into a Linq entity. Once it’s an entity is can be handled in the same way as any other entity.
What’s more, wouldn’t it be good if we could get a reference to an SPListItem from an existing entity? Why you say, I though Linq killed the SPListItem? If only it were true! Unfortunately Linq only killed indexed field values, for everything else I’m afraid we still have to break open the behemoth SPListItem.
So how can it be done?
Lets take a quick look at the classes that SPMetal generates to see where we can extend them:
public partial class Item : ITrackEntityState, ITrackOriginalValues, INotifyPropertyChanged, INotifyPropertyChanging
{
private System.Nullable<int> _id;
private System.Nullable<int> _version;
private string _path;
private EntityState _entityState;
private IDictionary<string, object> _originalValues;
private string _title;
#region Extensibility Method Definitions
//snipped for brevity
#endregion
EntityState ITrackEntityState.EntityState
{
//snipped for brevity
}
IDictionary<string, object> ITrackOriginalValues.OriginalValues
{
//snipped for brevity
}
public Item()
{
this.OnCreated();
}
[ColumnAttribute(Name = "ID", Storage = "_id", ReadOnly = true, FieldType = "Counter")]
public System.Nullable<int> Id
{
//snipped for brevity
}
[ColumnAttribute(Name = "owshiddenversion", Storage = "_version", ReadOnly = true, FieldType = "Integer")]
public System.Nullable<int> Version
{
//snipped for brevity
}
[ColumnAttribute(Name = "FileDirRef", Storage = "_path", ReadOnly = true, FieldType = "Lookup", IsLookupValue = true)]
public string Path
{
//snipped for brevity
}
[ColumnAttribute(Name = "Title", Storage = "_title", Required = true, FieldType = "Text")]
public virtual string Title
{
//snipped for brevity
}
public event PropertyChangedEventHandler PropertyChanged;
public event PropertyChangingEventHandler PropertyChanging;
protected virtual void OnPropertyChanged(string propertyName)
{
//snipped for brevity
}
protected virtual void OnPropertyChanging(string propertyName, object value)
{
//snipped for brevity
}
}
There are a few things to notice about the classes that SPMetal generates. Firstly, they mimic content type inheritance in SharePoint. So everything derives from Item. Secondly, and most importantly for this code – they use attributes on each properties to map the values to SPListItem fields. You’ll notice that each property has a Microsoft.SharePoint.Linq.ColumnAttribute attribute attached to it containing the names of a private field that stores a copy of the data and details of the SPListItem field and data-type that the value maps to.
Using this information, we can add a new constructor to the Item object that accepts an SPListItem as a parameter:
public partial class Item
{
public Item(SPListItem source)
: base()
{
//loop through the properties
PropertyInfo[] properties = this.GetType().GetProperties();
foreach (PropertyInfo pi in properties)
{
//Get an array of the ColumnAttributes
object[] attributes = pi.GetCustomAttributes(typeof(Microsoft.SharePoint.Linq.ColumnAttribute),false);
foreach (ColumnAttribute att in attributes)
{
//get details of the private field
FieldInfo storage = this.GetType().GetField(att.Storage,BindingFlags.NonPublic|BindingFlags.Instance);
Type current = this.GetType();
//since the entity objects mimic the Content Tope hierarchy, the private field may be on a base object
while (storage == null)
{
//iterate through the base classes until we find the field
current = current.BaseType;
if (current == null) break;
storage = current.GetField(att.Storage, BindingFlags.NonPublic | BindingFlags.Instance);
}
if (storage != null)
{
//if we've found the field set it's value to the indexed property from the source SPListItem
storage.SetValue(this, source[att.Name]);
}
}
}
}
}
Now all we need to do is add constructors for the entity classes that we want to have this functionality ( I suppose rather than using the constructor we could add a Load method that would automatically be inherited by event class?):
public partial class SampleLIstItem
{
public SampleLIstItem(SPListItem source)
: base(source)
{
}
}
A better event handler?
Using these modified entity objects we can write event handlers like this:
public override void ItemUpdating(SPItemEventProperties properties)
{
base.ItemUpdating(properties);
SampleLIstItem test = new SampleLIstItem(properties.ListItem);
if (test.MyColumn1 == "My comparison value")
{
DoSomething();
}
test.DemoColumn1 = "some new value";
this.EventFiringEnabled = false;
//We need a datacontext to update the list item
using (EventHandlerDemoDataContext ctx = new EventHandlerDemoDataContext(properties.WebUrl))
{
ctx.SampleLIst.Attach(test);
ctx.SubmitChanges();
}
this.EventFiringEnabled = true;
}
Maybe not a million miles away from our initial example but the major benefit of this syntax is that it’s type safe and changing field names won’t break your code as long as you update the entity classes. No longer do we need to spend time creating business objects, we can let SPMetal do most of the work, add in a few lines of code and the job’s a good ‘un.
What about getting a reference to the SPListItem?
The other minor extension that I wanted to make was to make it possible to get a reference to an SPListItem from an entity object. There are a few ways that this could be done. We can extend the entity object to contain any data that we need, so we could store the web.Id and list.Id and get a reference using those. There’s a lot of work in that though and it may very well cause problems with some of the Linq functionality. (Since it’s possible to disconnect an entity from one list, change the list or move it to another site, then reattach it for example). The easiest and safest way to implement this functionality is to add a method that takes a reference to the SPList that contains the item.
We can simply add this code to the Item entity:
public SPListItem GetListItem(SPList list)
{
if (_id.HasValue)
{
return list.GetItemById(_id.Value);
}
else
{
return null;
}
}
Conclusion
Linq to SharePoint makes development for SharePoint so much simpler. We can spend less time worrying about field names and guids and more time writing code. It’s now actually possible to refactor code in SharePoint without the certainty of something breaking because of an indexed property name being wrong or problems with trying to perform invalid type conversions. But even better than that, the whole thing can be completely extended to provide a sensible business logic layer for your application with the majority of the work being done by SPMetal.
SharePoint 2010 is shaping up nicely to be the development platform that we wished MOSS was in my opinion.