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:
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();
}
[Microsoft.SharePoint.Linq.ListAttribute(Name="Customized Reports")]
public Microsoft.SharePoint.Linq.EntityList<Document> CustomizedReports {
get {
return this.GetList<Document>("Customized Reports");
}
}
[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.
[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.
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<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>
="1.0"="utf-8"
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<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
.
[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.
[Microsoft.SharePoint.Linq.ListAttribute(Name="Companies")]
public Microsoft.SharePoint.Linq.EntityList<CPCompany> Companies {
get {
return this.GetList<CPCompany>("Companies");
}
}
[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.
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.
="1.0"="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.
="1.0"="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
.
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.
="1.0"="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.
="1.0"="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.
[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:
="1.0"="utf-8"
<Web xmlns="http://schemas.microsoft.com/SharePoint/2009/spmetal" Class="CodeProjectDemo">
<List Name="Contacts" Member="CPContacts"/>
<List Name="Company"/>
<ExcludeOtherLists/>
</Web>
[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.
="1.0"="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:
[Microsoft.SharePoint.Linq.ContentTypeAttribute
(Name="CPContact", Id="0x0100524A1BB0926F4201B7C8E3C366D36FCD")]
public partial class CodeProjectContact : Item {
rather than this one without the ContentType
element:
[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:
<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.
[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
.
<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.
[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.
="1.0"="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.
[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:
<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.
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.
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