使用 SPMetal
使用 SPMetal 生成 Linq to SharePoint 类
引言
SharePoint 2010 包含许多更新和增强功能,使 SharePoint 开发人员的生活更加轻松。其中一项增强功能是 Linq to SharePoint,可用于访问 SharePoint 列表和库,而无需使用 CAML。SPMetal 是用于创建实体框架类的工具,从而实现这一点。本文旨在概述和解释如何快速为您的项目生成实体类。
SPMetal
SPMetal 是一个命令行工具,类似于 SQLMetal。后者用于为 SQL Server 数据库表生成实体类,而前者则根据与之关联的内容类型为 SharePoint 列表和库生成实体类。该工具可以在 SharePoint 安装文件夹中找到,即 %ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\BIN。
SPMetal 使用多个选项来控制生成和输出。第一个选项是 web,用于指定将用于生成输出的站点。
SPMetal /web:http://myserver
当上述命令执行时,SPMetal 将查看指定 web 中的所有列表,并为它们以及关联的内容类型生成一个实体类。然而,为每个列表(包括内置的默认列表)生成一个类可能不是期望的结果。在本文的后面,我将介绍如何限制使用的列表。
命令的输出可以通过另外两个选项来控制:code 和 language。如果未使用 code,则 language 是必需的。Language 有两个有效值:csharp 和 vb,用于指定将以何种语言生成类。code 选项用于指定输出的路径和文件名;如果未指定,输出将到标准输出,即控制台窗口。如果指定了 code 选项,language 选项是可选的,SPMetal 将尝试从文件扩展名(cs 或 vb)推断语言。
SPMetal /web:http://myserver /code:c:\myproject\spmetal.cs
对使用 Blank Site 模板分区的站点执行此命令将在 C:\MyProject 文件夹中创建一个文件 SPMetal.cs,其中包含如下代码:
//------------------------------------------------------------------------------
// <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");
}
}
}
从代码片段可以看出,这是一个生成的文件,不应直接编辑。类名 SpmetalDataContext
取自 code 选项使用的文件名(本例中为 spmetal),并派生自 Microsoft.SharePoint.Linq.DataContext
。任何使用过 Linq 的人都应该熟悉它,因为它提供了与 Microsoft.Data.Linq.DataContext
相同的功能,只是在 SharePoint 环境中而不是 SQL Server 环境中。对于那些从未使用过 Linq 的人来说,这个类提供了对 SharePoint 列表和库的访问和更改跟踪。关于 Linq 的主题已经写了整本书,所以我在这里不深入讨论其细节。
您可以看到 Blank Site 模板附带的两个库,Customized Reports 和 Style Library,已通过返回 Microsoft.SharePoint.Linq.EntityList
的属性表示。这些列表的描述也已作为代码注释添加。稍后,我将描述属性名称是如何派生和创建的。
您还可以看到此类中的构造函数接受一个参数,该参数标识用于执行任何查询的上下文的站点 URL。通常,这将是您的代码正在执行的当前站点,但是,可以传入并使用其他 URL;前提是它包含具有相同内容类型和列的相同列表。
下面显示的片段是由与列表关联的内容类型创建的 Entity
类。当然,由于 SharePoint 中的所有内容类型都派生自 Item
,因此将始终创建此类。您再次可以看到描述已作为代码注释添加。ContentTypeAttribute
定义了内容类型的名称及其 ID。DerivedEntityClassAttribute
描述了将从该实体派生的其他实体。当然,如果我们稍后看到添加了更多内容类型,将添加更多的 DerivedEntityClassAttributes
。
/// <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");
}
}
}
...
实体类还实现了 Linq 用于启用更改跟踪的多个接口。您还可以在此处看到与内容类型关联的列由具有后端字段的属性表示。Microsoft.SharePoint.Linq.ColumnAttribute
用于描述站点列,正如您所推断的,name 参数是列名。Storage 参数标识后端字段,在此情况下将其标记为 ReadOnly
,因为它是一个标识符。列类型由 FieldType
参数标识,并使用标准的 SharePoint 定义类型,如 Text 和 Numeric。有趣的是,您可以看到所有数字字段都使用 Nullable
类型,即使它们是必需字段。
示例内容类型
在此演示中,我将创建两个简单内容类型,以展示 SPMetal 和 Linq to SharePoint 的功能和用法。
<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>
您可以看到 Contact
内容类型(我将其命名为 CPContact
以避免与现有的名为 Contact
的 SharePoint 内容类型名称冲突)仅包含联系人的名字、姓氏和头衔字段。Company 内容类型(为与 CPContact
一致而命名为 CPCompany
)仅包含公司名称的字段。如本文的示例代码所示,将在列表定义中添加 CPCompany
和 CPContact
之间的查找。
在这些站点列、内容类型和列表部署后,如果我们再次执行 SPMetal 命令,我们将发现 DerivedEnityClassAttributes
已添加到 Item
实体类中,用于新的内容类型 CPContact
和 CPCompany
。
/// <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 {
新列表的属性也已添加到 DataContext
类中。您还将注意到仍会为默认列表 Customized Reports 和 Style Library 生成属性。我们稍后将看到如何纠正这一点。
/// <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");
}
}
如果您注意到,生成的类没有关联的命名空间。要添加它,您需要使用 SPMetal 的另一个选项:namespace。
SPMetal /web:http://myserver /code:c:\myproject\spmetal.cs
/namespace:CodeProject.SharePoint.Demo
执行此命令后,指定的命名空间 CodeProject.SharePoint.Demo
将添加到生成的类中。
namespace CodeProject.SharePoint.Demo {
using System;
public partial class SPMetalDemoDataContext :
Microsoft.SharePoint.Linq.DataContext {
...
SPMetal 还有一些其他选项可以使用,但上述选项是最常见的。另一个为 SPMetal 提供极大控制的选项是 parameters。
SPMetal 参数文件
与前面的选项一样,parameters 接受一个 string
,但这个 string
是一个 XML 格式文件的路径,该文件向 SPMetal 提供指令。该文件的架构可以在 %Program Files%\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\XML 找到。
<?xml version="1.0" encoding="utf-8" ?>
<Web xmlns="http://schemas.microsoft.com/SharePoint/2009/spmetal">
</Web>
如您所见,此文件的根元素是 Web,除了标准的 namespace 属性外,还有两个其他属性:AccessModifier
和 Class
。默认情况下,如果未指定,SPMetal 将生成 public
类。如果您需要更改此设置,请使用 AccessModifier
属性并指定 Internal
。唯一可用的值是 Public
或 Internal
。如前所述,SPMetal 使用 code 选项中的文件名来命名生成的 DataContext
类,即 SPMetalDemoDataContext
。要更改此设置,请使用 Class
属性。
<?xml version="1.0" encoding="utf-8" ?>
<Web xmlns=http://schemas.microsoft.com/SharePoint/2009/spmetal
Class="CodeProjectDemo">
</Web>
在此处使用 parameters 选项执行 SPMetal
SPMetal /web:http://myserver /code:c:\myproject\spmetal.cs
/namespace:CodeProject.SharePoint.Demo /parameters:DemoParams.xml
将生成与之前相同的文件,但类名现已更改为 CodeProjectDemo
。
namespace CodeProject.SharePoint.Demo {
using System;
public partial class CodeProjectDemo : Microsoft.SharePoint.Linq.DataContext {
...
要从生成的文件中删除内置列表 Customized Reports 和 Style Library,您可以使用 ExcludeList
元素。
<?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>
另一种实现此目的的方法是使用 List
元素和 ExcludeOtherLists
元素。
<?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>
使用此参数文件执行 SPMetal 后,仍会生成文件,但其中只有 List
元素中指定的列表的类和属性,其他所有列表(包括内置列表)都不会生成。如果您只想为特定列表生成类,或者您使用了一个包含许多内置列表和库但希望排除它们的模板,而无需添加多个 ExcludeList
元素,这将非常有用。默认情况下,SPMetal 不为隐藏列表生成类,但是,使用 List
元素,您可以为任何您想要的列表生成类。
需要注意的是,拼写很重要。如果 Name
属性指定的列表名称找不到,它将被忽略,在 List
元素的情况下不会生成类或属性,在 ExcludeList
的情况下也不会将其排除。
如您所见,SPMetal
使用列表名称作为其生成的属性名称。
/// <summary>
/// Instance contacts list
/// </summary>
[Microsoft.SharePoint.Linq.ListAttribute(Name="Contacts")]
public Microsoft.SharePoint.Linq.EntityList<CPContact> Contacts {
get {
return this.GetList<CPContact>("Contacts");
}
}
要更改此设置,您可以使用 Member
属性。
<?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>
/// <summary>
/// Instance contacts list
/// </summary>
[Microsoft.SharePoint.Linq.ListAttribute(Name="Contacts")]
public Microsoft.SharePoint.Linq.EntityList<CPContact> CPContacts {
get {
return this.GetList<CPContact>("Contacts");
}
}
请注意,这不会更改 Item
派生类的名称,因为它基于 ContentType
;这只会更改 DataContext
类中访问器属性的名称。要更改从 ContentType
生成的类的名称,您需要使用 ContentType
元素。
<?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>
再次,使用此参数文件执行 SPMetal 将生成下面的类。
/// <summary>
/// Demo content type for contacts
/// </summary>
[Microsoft.SharePoint.Linq.ContentTypeAttribute
(Name="CPContact", Id="0x0100524A1BB0926F4201B7C8E3C366D36FCD")]
public partial class CodeProjectContact : Item {
而不是这个没有 ContentType
元素
/// <summary>
/// Demo content type for contacts
/// </summary>
[Microsoft.SharePoint.Linq.ContentTypeAttribute
(Name="CPContact", Id="0x0100524A1BB0926F4201B7C8E3C366D36FCD")]
public partial class CPContact : Item {
列/字段命名
如我们所见,SPMetal 将为与列表关联的 ContentType
生成一个类。类中会为 ContentType
中的每个站点列添加带有 private
后端字段的属性。SPMetal 使用 DisplayName
属性来命名这些属性,因此必须在字段定义或 ContentType
定义的某个地方为该列指定 DisplayName
。如果未使用 DisplayName
并执行 SPMetal,它将产生以下错误。
Error: Value cannot be null.
Parameter name: input
在确定属性和后端字段的名称时,SPMetal
会首先查找 ContentType
定义中列的 DisplayName
,然后查找 Field
定义。对于内置的站点列,它会查看 StaticName
属性。还应注意,带有空格的 DisplayNames
将被移除空格。例如,一个具有此定义的站点列。
<Field ID="{83abdcd4-9591-4dde-a1be-e141d73a87d1}" Name="ContactType"
DisplayName="Type of contact" Type="Text" Group="CodeProject" />
将被生成为这样,请注意空格已被移除,属性名称使用了 Pascal 命名法。
[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 列来将 CPContact
与 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" />
像以前一样执行 SPMetal 后,将生成一个额外的类来处理关联。您可以看到下面,新类派生自之前为 CPContact
ContentType
生成的类,并为 Companies 列表中的 CPCompany
项添加了一个查找属性。
[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);
}
}
这个类的名称是从列表名称 Contacts
和内容类型名称 Contact
生成的。这可能会产生一些有趣且冗长的名称。要让 SPMetal
生成一个可能更有意义的名称,您可以再次使用参数文件。
<?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>
现在使用此参数文件执行 SPMetal 命令将生成此类。
/// <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);
}
}
没有办法让 CPCompany
属性直接添加到 CPContact
类中。但是,您将看到,通过使用部分类,您可以自己公开一个属性。
选择列
对于选择字段,SPMetal 将生成一个 enum
,其中包含所有表示的值,以及两个额外的值:none 和 invalid。将 ContactType
列更新为 Choice
列后。
<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>
再次执行 SPMetal 命令,您将在此生成的文件中找到此 enum
,并且属性已更新。
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");
}
}
}
请注意,这里的 enum
遵循位标志约定,即使列本身是单值的,也可以这样使用。
排除列
就像上面的列表一样,可以使用 ExcludeColumn
和 ExcludeOtherColumns
元素将单个列排除在生成文件之外。我没有提供此示例,因为它与上面的列表示例足够相似。
包含列表和列
SPMetal 还包括包含通常不会生成的列表和列(如隐藏列表和站点列)的功能。可以使用 IncludeHiddenLists
和 IncludeHiddenColumns
元素来实现这一点。同样,我没有包含此示例,因为它遵循与先前示例相同的模式。
部分类
.NET Framework 2.0 引入了部分类的概念,它们广泛用于 Linq,以实现更具扩展性的体系结构。对于不熟悉它们的人来说,部分类包含在同一个命名空间中,并具有相同的名称。它们可以位于项目中的不同文件夹的不同文件中。这允许隔离功能,并提供了一种在未来扩展类的方法。在编译时,这些类被整合并编译成一个单一类。
namespace CodeProject.SharePoint.Demo
{
public partial class CompanyContact
{
public string FullName
{
get { return FirstName + " " + LastName; }
}
}
}
在这里,我扩展了 SPMetal 生成的 CompanyContact
类,以包含 FullName
属性,作为使用该类的任何人的便利。这是一个非常简单的示例,当然您可以根据需要添加更多方法和属性。
结论
本文解释了 SPMetal 工具以及如何使用它从 SharePoint 列表生成实体类,以便您可以使用 Linq to SharePoint。当然,还有更多细节我可以深入探讨,但我希望这为您继续学习打下了基础。
历史
- 首次发布:2011 年 3 月 14 日