Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Using SPMetal

5.00/5 (5 votes)
15 Mar 2011CPOL11 min read 58K   435  
Using SPMetal to generate Linq to SharePoint classes

Introduction

SharePoint 2010 has included many updates and enhancements that have made life easier for SharePoint developers. One of these enhancements is Linq to SharePoint which can be used to access SharePoint lists and libraries without the need to use CAML. SPMetal is the tool used to create Entity Framework classes making this possible. This article attempts to give you an overview and understanding of how it can used to quickly generate entity classes for your project.

SPMetal

SPMetal is a command-line tool similar to SQLMetal. While the latter is used to generate entity classes for SQL Server database tables, the former is used to generate entity classes from SharePoint lists and libraries based on the content type(s) associated with them. The tool can be found in SharePoint installation folder, %ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\BIN.

SPMetal uses a number of options to control generation and output. The first option is web and is used to specify which site will be used to generate the output.

SPMetal /web:http://myserver

When the above command is executed, SPMetal will look at all lists in the specified web and generate an entity class for them and the associated content types. Generating a class for every list, including the built-in default lists, however, may not be desirable. Later in this article, I will show how to limit which lists are used.

The output of the command can be controlled by two other options, code and language. If code is not used, then language is required. Language has two valid values, csharp and vb, and is used to specify which language the classes will be generated in. The code option is used to specify the path and file name for the output, if not specified the output will be to standard output, i.e., the console window. If the code option is specified, the language option is optional and SPMetal will attempt to infer the language from the file extension, cs or vb.

SPMetal /web:http://myserver  /code:c:\myproject\spmetal.cs

Executing this command against a site partitioned with the Blank Site template will create the file SPMetal.cs in the folder C:\MyProject with the code shown below:

C#
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:2.0.50727.5420
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System;

public partial class SpmetalDataContext : Microsoft.SharePoint.Linq.DataContext {
	
	#region Extensibility Method Definitions
	partial void OnCreated();
	#endregion
	
	public SpmetalDataContext(string requestUrl) : 
			base(requestUrl) {
		this.OnCreated();
	}
	
	/// <summary>
	/// This Document library has the templates to create 
	/// Web Analytics custom reports for this site collection
	/// </summary>
	[Microsoft.SharePoint.Linq.ListAttribute(Name="Customized Reports")]
	public Microsoft.SharePoint.Linq.EntityList<Document> CustomizedReports {
		get {
			return this.GetList<Document>("Customized Reports");
		}
	}
	
	/// <summary>
	/// Use the style library to store style sheets, such as CSS or XSL files. 
	/// The style sheets in this gallery can be used by this site or 
	/// any of its subsites.
	/// </summary>
	[Microsoft.SharePoint.Linq.ListAttribute(Name="Style Library")]
	public Microsoft.SharePoint.Linq.EntityList<Document> StyleLibrary {
		get {
			return this.GetList<Document>("Style Library");
		}
	}
}

As you can see from the code snippet, this was a generated file and is not meant to be directly edited. The class name, SpmetalDataContext, is taken from the filename used for the code option, spmetal in this case, and is derived from Microsoft.SharePoint.Linq.DataContext. This should be familiar to anyone who has used Linq before as it provides the same functionality as Microsoft.Data.Linq.DataContext, only in a SharePoint environment rather than a SQL Server environment. For those who have not used Linq, this class provides access and change tracking for SharePoint lists and libraries. Entire books have been dedicated to the subject of Linq so I won't delve into its details here.

You can see the two libraries that come with the Blank Site template, Customized Reports and Style Library, have been represented with properties that return Microsoft.SharePoint.Linq.EntityList. The descriptions for these lists have also been added as code comments. Later, I'll describe how the property names are derived and created.

You can also see the constructor from this class takes a parameter that identifies the URL of the site that will be used for the context of any queries to be executed. Generally, this will be the current site where your code is being executed, however, a different URL could be passed in and used; provided, of course, that it contained the same lists with the same content types and columns.

The snippet shown below is an Entity class that is created from the content type associated with the list. Of course, since all content types within SharePoint are derived from Item, this class will always be created. Once again, you can see the description has been added as code comments. The ContentTypeAttribute defines the name of the content type and its Id. The DerivedEntityClassAttribute describes what other Entities will be derived from this one. Of course, if more content types are added, as we'll see later, there will be more DerivedEntityClassAttributes added.

C#
/// <summary>
/// Create a new list item.
/// </summary>
[Microsoft.SharePoint.Linq.ContentTypeAttribute(Name="Item", Id="0x01")]
[Microsoft.SharePoint.Linq.DerivedEntityClassAttribute(Type=typeof(Document))]
public partial class Item : Microsoft.SharePoint.Linq.ITrackEntityState, 
    Microsoft.SharePoint.Linq.ITrackOriginalValues, 
    System.ComponentModel.INotifyPropertyChanged, 
    System.ComponentModel.INotifyPropertyChanging {
	
	private System.Nullable<int> _id;
	
	private System.Nullable<int> _version;
	
	private string _path;
	
	private Microsoft.SharePoint.Linq.EntityState _entityState;
	
	private System.Collections.Generic.IDictionary<string, object> _originalValues;
	
	private string _title;

    ...

    	[Microsoft.SharePoint.Linq.ColumnAttribute
		(Name="ID", Storage="_id", ReadOnly=true, FieldType="Counter")]
	public System.Nullable<int> Id {
		get {
			return this._id;
		}
		set {
			if ((value != this._id)) {
				this.OnPropertyChanging("Id", this._id);
				this._id = value;
				this.OnPropertyChanged("Id");
			}
		}
	}

    ...

The entity classes also implement a number of interfaces that are used by Linq to enable change tracking. You can also see here the columns associated with the content type are represented by properties with backing fields. The Microsoft.SharePoint.Linq.ColumnAttribute is used to describe the site column and, as you can infer, the name parameter is the column name. The Storage parameter identifies the backing field, and it is flagged as ReadOnly in this case since it is the identifier. The column type is identified by the FieldType parameter and uses the standard SharePoint defined types, such as Text and Numeric. Interestingly, you can see that all numeric fields use the Nullable type, even if they are required fields.

Sample Content Types

For this demo, I'll create two simple content types to show the features and usage of SPMetal and Linq to SharePoint.

XML
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <!-- Parent ContentType: Item (0x01) -->
  <ContentType ID="0x0100bd31a6951c0243faa9ad062940325d8a"
               Name="CPCompany"
               Group="CodeProject"
               Description="Demo content type for companies"
               Inherits="TRUE"
               Version="0">
    <FieldRefs>
      <FieldRef ID="{e05520db-acd3-49aa-89be-17b86b98aa7b}" Name="CompanyName" />
    </FieldRefs>
  </ContentType>
</Elements>

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <!-- Parent ContentType: Item (0x01) -->
  <ContentType ID="0x0100524a1bb0926f4201b7c8e3c366d36fcd"
               Name="CPContact"
               Group="CodeProject"
               Description="Demo content type for contacts"
               Inherits="TRUE"
               Version="0">
    <FieldRefs>
      <FieldRef ID="{cbad7981-71e3-4c86-a579-cc7746dcc4a7}" Name="FirstName" />
      <FieldRef ID="{9d387fe6-8fb6-4437-a32e-a89e2d7d36ec}" Name="LastName" />
      <FieldRef ID="{77850ecf-8fce-4a97-8183-d85638145609}" Name="ContactTitle" />
    </FieldRefs>
  </ContentType>
</Elements>

As you can see the Contact content type, which I've named CPContact to avoid a name clash with the existing SharePoint content type named Contact, simply contains fields for the contacts first name, last name and title. And the Company content type, named CPCompany for consistency with CPContact, simply contains a field for the name of the company. A lookup between CPCompany and CPContact will be added in the list definition as you can see in the sample code for this article.

After these site columns, content types and lists have been deployed if we execute the SPMetal command once again we'll find DerivedEnityClassAttributes have been added to the Item entity class for the new content types, CPContact and CPCompany.

C#
/// <summary>
/// Create a new list item.
/// </summary>
[Microsoft.SharePoint.Linq.ContentTypeAttribute(Name="Item", Id="0x01")]
[Microsoft.SharePoint.Linq.DerivedEntityClassAttribute(Type=typeof(CPCompany))]
[Microsoft.SharePoint.Linq.DerivedEntityClassAttribute(Type=typeof(CPContact))]
[Microsoft.SharePoint.Linq.DerivedEntityClassAttribute(Type=typeof(Document))]
public partial class Item : Microsoft.SharePoint.Linq.ITrackEntityState, 
    Microsoft.SharePoint.Linq.ITrackOriginalValues, 
    System.ComponentModel.INotifyPropertyChanged, 
    System.ComponentModel.INotifyPropertyChanging {     

New properties for the lists have also been added to the DataContext class. You will also note properties for the default lists, Customized Reports and Style Library, are still being generated. We'll see shortly how to correct this.

C#
/// <summary>
/// Instance of companies list
/// </summary>
[Microsoft.SharePoint.Linq.ListAttribute(Name="Companies")]
public Microsoft.SharePoint.Linq.EntityList<CPCompany> Companies {
	get {
		return this.GetList<CPCompany>("Companies");
	}
}
	
/// <summary>
/// Instance contacts list
/// </summary>
[Microsoft.SharePoint.Linq.ListAttribute(Name="Contacts")]
public Microsoft.SharePoint.Linq.EntityList<CPContact> Contacts {
	get {
		return this.GetList<CPContact>("Contacts");
	}
}

If you notice, the generated class does not have a namespace associated with it. To add this, you'll need to use another of the SPMetal option, namespace.

SPMetal /web:http://myserver  /code:c:\myproject\spmetal.cs 
/namespace:CodeProject.SharePoint.Demo

After executing this command the namespace specified, CodeProject.SharePoint.Demo, will be added to the generated class.

C#
namespace CodeProject.SharePoint.Demo {
using System;

public partial class SPMetalDemoDataContext :
    Microsoft.SharePoint.Linq.DataContext {
...

There are a few more options that can be used with SPMetal, however, those above are the most common. Another option that gives you a great deal of control over SPMetal is parameters.

SPMetal Parameter File

Just like the previous options, parameters takes a string but this string is the path to an XML formatted file that gives instructions to SPMetal. The scheme for this file can be found at %Program Files%\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\XML.

XML
<?xml version="1.0" encoding="utf-8" ?>
<Web xmlns="http://schemas.microsoft.com/SharePoint/2009/spmetal">
</Web>

As you can see, here the root element for this file is Web and in addition to the standard namespace attribute has two other attributes, AccessModifier and Class. By default, if not specified, SPMetal will generate the classes as public. If you need this to be changed, use the AccessModifier attribute and specify Internal. The only available values are Public or Internal. As you have seen earlier, SPMetal uses the filename from the code option to name the generated DataContext class, SPMetalDemoDataContext. To change this, use the Class attribute.

XML
<?xml version="1.0" encoding="utf-8" ?>
<Web xmlns=http://schemas.microsoft.com/SharePoint/2009/spmetal 
Class="CodeProjectDemo">
</Web>

Executing SPMetal with the parameters option here

SPMetal /web:http://myserver /code:c:\myproject\spmetal.cs 
/namespace:CodeProject.SharePoint.Demo /parameters:DemoParams.xml

will generate the same file as before, however, the class has now been named CodeProjectDemo.

C#
namespace CodeProject.SharePoint.Demo {
	using System;	
	
	public partial class CodeProjectDemo : Microsoft.SharePoint.Linq.DataContext {
    ...

To remove the built-in lists, Customized Reports and Style Library, from the generated file you would use the ExcludeList element.

XML
<?xml version="1.0" encoding="utf-8" ?>
<Web xmlns="http://schemas.microsoft.com/SharePoint/2009/spmetal">
  <ExcludeList Name="Customized Reports"/>
  <ExcludeList Name="Style Library"/>
</Web>

Another way to accomplish this could be to use List elements and the ExcludeOtherLists element.

XML
<?xml version="1.0" encoding="utf-8" ?>
<Web xmlns=http://schemas.microsoft.com/SharePoint/2009/spmetal 
	Class="CodeProjectDemo">
  <List Name="Contacts"/>
  <List Name="Company"/>
  <ExcludeOtherLists/>
</Web>

Executing SPMetal with this parameter file still causes the file to be generated with only classes and properties for the lists specified in the List elements, all others, including the built-in lists will not be generated. This is useful if you want to generate a class for a specific list only or perhaps you have used a template with many built-in lists and libraries you wish to exclude without adding multiple ExcludeList elements. By default, SPMetal does not generate classes for hidden lists, however, using the List element, you can generate a class for any you wish.

It should be noted that spelling is important. If the name of the list, as specified by the Name attribute, can't be found it will simply be ignored, no class or properties will be generated in the case of the List element nor will it be excluded in the case of the ExcludeList element.

As you have seen, SPMetal uses the name of the list as the name of the property it generates.

C#
/// <summary>
/// Instance contacts list
/// </summary>
[Microsoft.SharePoint.Linq.ListAttribute(Name="Contacts")]
public Microsoft.SharePoint.Linq.EntityList<CPContact> Contacts {
	get {
		return this.GetList<CPContact>("Contacts");
	}
}

To change this, you would use the Member attribute:

XML
<?xml version="1.0" encoding="utf-8" ?>
<Web xmlns="http://schemas.microsoft.com/SharePoint/2009/spmetal" Class="CodeProjectDemo">
  <List Name="Contacts"  Member="CPContacts"/>
  <List Name="Company"/>
  <ExcludeOtherLists/>
</Web>
C#
/// <summary>
/// Instance contacts list
/// </summary>
[Microsoft.SharePoint.Linq.ListAttribute(Name="Contacts")]
public Microsoft.SharePoint.Linq.EntityList<CPContact> CPContacts {
	get {
		return this.GetList<CPContact>("Contacts");
	}
}

Note this does not change the name of the Item derived class since it is based on the ContentType, this only changes the name of the accessor property in the DataContext class. To change the name of the class generated from the ContentType you need to use the ContentType element.

XML
<?xml version="1.0" encoding="utf-8" ?>
<Web xmlns="http://schemas.microsoft.com/SharePoint/2009/spmetal" Class="CodeProjectDemo">
  <List Name="Contacts"  Member="CPContacts"/>
  <List Name="Company"/>
  <ExcludeOtherLists/>
  <ContentType Name="CPContact" Class="CodeProjectContact"/>
</Web>

Once again, executing SPMetal using this parameter file will generate the below class:

C#
/// <summary>
/// Demo content type for contacts
/// </summary>
[Microsoft.SharePoint.Linq.ContentTypeAttribute
	(Name="CPContact", Id="0x0100524A1BB0926F4201B7C8E3C366D36FCD")]
public partial class CodeProjectContact : Item {

rather than this one without the ContentType element:

C#
/// <summary>
/// Demo content type for contacts
/// </summary>
[Microsoft.SharePoint.Linq.ContentTypeAttribute
(Name="CPContact", Id="0x0100524A1BB0926F4201B7C8E3C366D36FCD")]
public partial class CPContact : Item {

Column/Field Naming

As we have seen, SPMetal will generate a class for the ContentType(s) associated with the lists. Properties with private backing fields are added to the class for each of the site columns in the ContentType. SPMetal uses the DisplayName attribute to name these properties and as such a DisplayName must be specified somewhere for the column, either in the field definition or the ContentType definition. If no DisplayName is used and SPMetal is executed, it will produce the following error:

Error: Value cannot be null.
Parameter name: input 

When determining a name for the property and backing field, SPMetal will first look to the ContentType definition for a DisplayName for the column, then to the Field definition. For built-in Site Columns, it will look at the StaticName attribute. It should also be noted that DisplayNames with spaces will have the spaces removed. For example, a Site Column with this definition:

XML
<Field ID="{83abdcd4-9591-4dde-a1be-e141d73a87d1}" Name="ContactType" 
DisplayName="Type of contact" Type="Text" Group="CodeProject" />

will be generated as this, notice the spaces where removed and the property name uses Pascal casing.

C#
[Microsoft.SharePoint.Linq.ColumnAttribute
(Name="ContactType", Storage="_typeOfContact", FieldType="Text")]
public virtual string TypeOfContact {
	get {
		return this._typeOfContact;
	}
	set {
		if ((value != this._typeOfContact)) {
			this.OnPropertyChanging("TypeOfContact", 
				this._typeOfContact);
			this._typeOfContact = value;
			this.OnPropertyChanged("TypeOfContact");
		}
	}
}

Lookup Columns

As is often the case, lists can be created with references to other lists. For this demo, I will add a Lookup column to associate a CPContact with a CPCompany.

XML
<Field ID="{A51710C4-B16C-4982-B0F7-DD4B1F053C9B}" Name="CompanyLookup" 
DisplayName="Company" Group="CodeProject" Type="Lookup" List="Lists/Companies" 
PrependId="TRUE" Required="TRUE" EnforceUniqueValues="FALSE" ShowField="ID"  /> 

After executing SPMetal as we have in the past, an additional class will be generated to handle the association. As you can see below, the new class is derived from the previously generated class for the CPContact ContentType and adds a property for the Lookup to the CPCompany item in the Companies list.

C#
[Microsoft.SharePoint.Linq.ContentTypeAttribute
(Name="Contact", Id="0x0100524A1BB0926F4201B7C8E3C366D36FCD", List="Contacts")]
public partial class ContactsContact : CPContact {
		
	private Microsoft.SharePoint.Linq.EntityRef<CPCompany> _company;
		
	
	[Microsoft.SharePoint.Linq.AssociationAttribute
		(Name="CompanyLookup", Storage="_company", 
    MultivalueType=Microsoft.SharePoint.Linq.AssociationType.Single, 
		List="Companies")]
	public CPCompany Company {
		get {
			return this._company.GetEntity();
		}
		set {
			this._company.SetEntity(value);
		}
	}

The name of this class is generated from the name of the list, Contacts, and the name of the ContentType, Contact. This can produce some interesting, and long, names. To have SPMetal generate a perhaps more meaningful name, you can once again use the parameter file.

XML
<?xml version="1.0" encoding="utf-8" ?>
<Web xmlns=http://schemas.microsoft.com/SharePoint/2009/spmetal 
Class="CodeProjectDemo">
  <List Name="Contacts" Member="CPContacts">
    <ContentType Name="Contact" Class="CompanyContact">
      <Column Name="CompanyLookup" Member="CPCompany"/>
    </ContentType>  
  </List>
  <List Name="Companies"/>
  <ExcludeOtherLists/>
</Web>

Executing the SPMetal command now will generate this class.

C#
/// <summary>
/// Demo content type for contacts
/// </summary>
[Microsoft.SharePoint.Linq.ContentTypeAttribute
	(Name="Contact", Id="0x0100524A1BB0926F4201B7C8E3C366D36FCD", List="Contacts")]
public partial class CompanyContact : CPContact {
		
	private Microsoft.SharePoint.Linq.EntityRef<CPCompany> _cPCompany;
		
	[Microsoft.SharePoint.Linq.AssociationAttribute
		(Name="CompanyLookup", Storage="_cPCompany", 
    MultivalueType=Microsoft.SharePoint.Linq.AssociationType.Single, List="Companies")]
	public CPCompany CPCompany {
		get {
			return this._cPCompany.GetEntity();
		}
		set {
			this._cPCompany.SetEntity(value);
		}
	}

There is no way to have the CPCompany property simply added to the CPContact class. However, as you will see, using a partial class you can expose a property yourself.

Choice Columns

For choice fields, SPMetal will generate an enum with all of the values represented, with two additional values, none an invalid. After updating the ContactType column to be a Choice column:

XML
<Field ID="{83abdcd4-9591-4dde-a1be-e141d73a87d1}" Name="ContactType"
  DisplayName="Type of contact" Type="Choice" Group="CodeProject">
  <CHOICES>
    <CHOICE>Sales Rep</CHOICE>
    <CHOICE>VP</CHOICE>
    <CHOICE>Pointy Haired Boss</CHOICE>
  </CHOICES>
</Field>

and executing the SPMetal command once again, you will find this enum added to the generated file and the property has been updated.

C#
public enum TypeOfContact : int {
		
	None = 0,
		
	Invalid = 1,
		
	[Microsoft.SharePoint.Linq.ChoiceAttribute(Value="Sales Rep")]
	SalesRep = 2,
		
	[Microsoft.SharePoint.Linq.ChoiceAttribute(Value="VP")]
	VP = 4,
		
	[Microsoft.SharePoint.Linq.ChoiceAttribute(Value="Pointy Haired Boss")]
	PointyHairedBoss = 8,
}

[Microsoft.SharePoint.Linq.ColumnAttribute(Name="ContactType", 
	Storage="_typeOfContact", FieldType="Choice")]
public System.Nullable<TypeOfContact> TypeOfContact {
	get {
		return this._typeOfContact;
	}
	set {
		if ((value != this._typeOfContact)) {
			this.OnPropertyChanging("TypeOfContact", this._typeOfContact);
			this._typeOfContact = value;
			this.OnPropertyChanged("TypeOfContact");
		}
	}
}

Notice here that the enum follows a bitflag convention and can be used as such even though the column itself is a single valued.

Excluding Columns

Just as with the lists above, individual columns can be excluded from the generated file using the ExcludeColumn and ExcludeOtherColumns element. I have not provided an example of this, however, as it is similar enough to the above list examples.

Including Lists and Columns

SPMetal also includes the ability to include lists and columns that would not otherwise be generated, such as hidden lists and site columns. The IncludeHiddenLists and IncludeHiddenColumns elements can be used for this. Again, I did not include an example of this since it follows the same pattern and previous examples.

Partial Class

.NET Framework 2.0 introduced the concept of Partial classes and they are used extensively in Linq to allow for a more extensible architecture. For those unfamiliar with them, Partial classes are contained within the same namespace and have the same name. They can be in separate files in separate folders in a project. This allows for functionality to be isolated and provides a means to extend a class in the future. During compile time, the classes are brought together and compiled into a single class.

C#
namespace CodeProject.SharePoint.Demo
{
    public partial class CompanyContact
    {
        public string FullName
        {
            get { return FirstName + " " + LastName;  }
        }
    }
}

Here, I have extended the CompanyContact class that was generated by SPMetal to include the FullName property as a convenience for anyone using this class. This is a very simple example and of course you can add more methods and properties as required.

Conclusion

This article presented an explanation of the SPMetal tool and how it can be used to generate Entity classes from your SharePoint lists so you can make use Linq to SharePoint. There is, of course, more detail I could have gone into, however, I hope this has give you a base to continue to learn from.

History

  • Initial posting: 3/14/2011

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)