在 SharePoint 2010 中访问数据

转:http://blog.banysky.net/?p=81001

数据访问的关键方法有哪些? |
使用查询类 |
使用 SPQuery |
使用 SPSiteDataQuery |
使用 LINQ to SharePoint |
LINQ to SharePoint 流程 |
LINQ to SharePoint 查询是如何执行的? |
为内容类型生成实体 |
在实体类中为关联建模 |
LINQ to SharePoint 的查询效率 |
存储库模式和 LINQ to SharePoint |
使用 BDC 对象模型 |
使用 Finder 方法来查询数据 |
使用 SpecificFinder 方法来查询数据 |
使用 AssociationNavigator 方法来查询数据 |
其他需要使用 BDC 对象模型的方案 |
结束语 |
更多信息

Microsoft® SharePoint® 2010 引入了几种新方法,帮助您以编程的方式与数据交互。最值得注意的是,引入 LINQ to SharePoint 之后,您就可以利用用户友好的语言集成查询 (LINQ) 语法来构建复杂的列表查询,而不必利用不太方便的协作应用程序标记语言 (CAML) 来构建查询。LINQ to SharePoint 和 CAML 现在都支持在查询中使用
join 谓词,这使得基于 SharePoint 列表的数据模型的功能和灵活性又向关系数据库迈近了一步。最新版本的 SharePoint 中的重要革新之一就是从用户的角度来说,内部和外部数据之间的界限变得模糊了。SharePoint 2010 中引入了外部列表,因此无论数据保存在 SharePoint 内容数据库中,还是保存在外部系统中,您都可以使用许多相同的技术来查询数据。

引入新的数据访问选项,也为开发人员带来了新的挑战和最佳做法。本章概述了各种服务器端数据访问方法,并介绍了每种方法的优势和潜在缺陷。

在 SharePoint 2010 中访问数据Note:

注意: 本章不讨论客户端数据访问技术,例如新的客户端对象模型和 REST 服务。这些技术涵盖了大量功能,将在单独一章中介绍。有关详细信息,请参阅第九章“客户端应用程序的数据访问”。

数据访问的关键方法有哪些?

SharePoint 2010 提供三种关键方法用于查询数据: CAML 查询、LINQ to SharePoint 和 BCS 对象模型。

  • CAML 查询SPQuerySPSiteDataQuery 类可用于构造和提交 CAML 查询,以便执行数据操作。CAML 有许多缺点,包括语法古怪、缺乏工具支持和难以调试。但是,CAML 仍然是数据操作的核心引擎,也是大部分方案中的最恰当选择。SharePoint 2010 中对 CAML 架构进行了扩展,使其支持 join 谓词。还可以使用
    SPQuery 类来查询外部列表。请注意,CAML 查询返回的结果是无类型的项。
  • LINQ to SharePoint。SharePoint 2010 允许您使用 LINQ 查询对 SharePoint 列表执行数据操作,并且支持 join 谓词。LINQ to SharePoint 的操作对象是代表列表项的强类型的实体,而且 SharePoint 2010 提供了一个命令行工具
    SPMetal 用于生成这些实体。LINQ to SharePoint 提供程序会在内部生成基础 CAML 查询,以便执行数据操作。
  • Business Connectivity Services (BCS) 对象模型。SharePoint 2010 提供了新的 BCS API,供您与外部数据交互。利用 BDC 对象模型,可通过编程的方式在业务数据连接 (BDC) 模型中导航数据实体和关联,以及调用这些实体上定义的构造型操作。

下表对各种不同的方案进行了概括,您可以在这些方案中使用每种方法进行数据访问。

情况

LINQ to SharePoint

SPQuery

SPSiteDataQuery

BDC 对象模型

查询网站内的 SharePoint 列表数据

在 SharePoint 2010 中访问数据

在 SharePoint 2010 中访问数据

对网站内的 SharePoint 列表使用联接操作

在 SharePoint 2010 中访问数据

在 SharePoint 2010 中访问数据

对网站集内各个网站中的 SharePoint 列表数据使用联接操作

在 SharePoint 2010 中访问数据

在 SharePoint 2010 中访问数据

聚合网站集内多个网站上的列表数据

在 SharePoint 2010 中访问数据

查询网站内的外部列表数据

在 SharePoint 2010 中访问数据

在 SharePoint 2010 中访问数据

在 BDC 模型中的实体之间导航关联(外部数据)

在 SharePoint 2010 中访问数据

访问来自外部数据的二进制流

在 SharePoint 2010 中访问数据

访问来自沙盒应用程序的外部数据(需要使用外部列表)

在 SharePoint 2010 中访问数据

查询会返回复杂类型的外部数据

在 SharePoint 2010 中访问数据

查询使用了非整数或 64 位整数 ID 字段的外部数据

在 SharePoint 2010 中访问数据

在 BDC 模型中的实体之间导航双向关联(外部数据)

在 SharePoint 2010 中访问数据

通过指定非 ID 字段的字段来定位实体(外部数据)

在 SharePoint 2010 中访问数据

查询其包含的字段未映射到 SPFieldType 的实体(外部数据)

在 SharePoint 2010 中访问数据

对外部数据执行批量操作

在 SharePoint 2010 中访问数据

本章的剩余篇幅将依次深入探讨其中每一种方法。

使用查询类

在 LINQ to SharePoint 问世之前,SPQuerySPSiteDataQuery 类是对 SharePoint 列表执行数据操作的主流方法。在很多方案中,这些类仍然能提供最有效的数据访问方法,并且在某些情况下,也是唯一可行的方法。

SPQuery 类和 SPSiteDataQuery 类都允许您使用协作应用程序标记语言 (CAML) 构造查询。随后可以使用
SPList 对象模型,对一个或多个 SharePoint 列表执行查询。本主题解释了何时使用哪个类,并介绍了可对您的实施提供指导的注意事项。

使用 SPQuery

SPQuery 类用于从特定列表中检索数据。大多数情况下,若要对列表执行数据操作,您应该使用 LINQ to SharePoint,而不是
SPQuery 类。但在有些情况下,SPQuery 仍然是最适合的数据访问方法,甚至是唯一选择。最值得注意的是,若要通过编程方式处理外部列表中的数据,SPQuery 类是唯一受支持的服务器对象模型方法。

在 SharePoint 2010 中,SPQuery 类已进行了扩展,允许您指定联接和投影字段。使用 SPQuery 类的大致流程如下:

  • 创建一个 SPQuery 实例。
  • 设置 SPQuery 实例上的属性,以便指定 CAML 查询以及各种附加查询参数(如果需要)。
  • SPList 实例上调用 GetItems 方法,将 SPQuery 实例作为参数传入。

下面的代码示例阐释了这一过程。


  1. SPListItemCollection results;
  2. var query = new SPQuery
  3. {
  4. Query = "[Your CAML query statement]",
  5. ViewFields = "[Your CAML FieldRef elements]",
  6. Joins = "[Your CAML Joins element]",
  7. ProjectedFields = "[Your CAML ProjectsFields element]"
  8. };
  9. results = SPContext.Current.Web.Lists["ListInstance"].GetItems(query);

本章不提供有关 CAML 语法的指南,也不提供有关如何配置 SPQuery 实例的说明,因为产品文档中的相关内容已经足够完善。但是,下面概要介绍了相关的关键属性:

  • Query 属性指定您要对列表实例执行的 CAML 查询。
  • ViewFields 属性将您的查询要返回的列指定为 CAML FieldRef 元素。
  • Joins 属性将您的查询的 join 谓词指定为 CAML Joins 元素。
  • ProjectedFields 属性将来自外部联接列表的字段定义为 CAML ProjectedFields 元素。这样,您就可以在
    ViewFields 属性和查询语句中引用这些字段。

下面的代码示例阐释了这些选项。请注意,为了便于阅读,ContentTypeId 值已经被缩短。


  1. var query = new SPQuery
  2. {
  3. Query = "
  4. <Query><Where><And><BeginsWith><FieldRef Name="ContentTypeId" />              <Value Type="ContentTypeId">0x0100...</Value></BeginsWith><Eq><FieldRef Name="SponsorTitle" /><Value Type="Lookup">David Pelton</Value></Eq></And></Where></Query>",
  5. ViewFields = "
  6. <FieldRef Name="Title" /><FieldRef Name="SponsorTitle" /><FieldRef Name="ProjectsLookupProject_x0020_Manager" />",
  7. Joins = "
  8. <Joins><Join Type="LEFT" ListAlias="ProjectsLookup"><!--List Name: Projects--><Eq><FieldRef Name="ProjectsLookup" RefType="ID" /><FieldRef List="ProjectsLookup" Name="ID" /></Eq></Join><Join Type="LEFT" ListAlias="Sponsor"><!--List Name: Sponsors--><Eq><FieldRef Name="Sponsor" RefType="ID" /><FieldRef List="Sponsor" Name="ID" /></Eq></Join></Joins>",
  9. ProjectedFields = "
  10. <ProjectedFields><Field Name="SponsorTitle" Type="Lookup" List="Sponsor" ShowField="Title" /><Field Name="ProjectsLookupProject_x0020_Manager" Type="Lookup"
  11. List="ProjectsLookup" ShowField="Project_x0020_Manager" /></ProjectedFields>"
  12. };
对一般的 SharePoint 列表使用 SPQuery

在以下方案中,您应该考虑使用 SPQuery 类,而不是 LINQ to SharePoint:

  • 当列表中的查找列指向网站集内另一个网站中的列表时。在这种情况下,SPQuery 允许您使用跨越这两个网站的 join 谓词。尽管通过一些附加配置,您可以使用 LINQ to SharePoint 来进行跨网站查询,但是生成实体类所需的过程会更复杂。默认情况下,LINQ to SharePoint 只会返回目标列表的
    ID 字段,在这种情况下,您就需要运行更多查询,以便从目标列表中检索相关的字段值。
  • 当性能是首要考虑事项时。使用 LINQ to SharePoint 会带来一些额外的开销,因为 LINQ 查询必须在运行时动态转换为 CAML。如果您要运行具有时效性的操作并且性能非常重要,可能需要考虑自行创建 CAML 并使用
    SPQuery 直接执行查询。一般来说,只有在极端情况下才需要使用这种方法。

    在 SharePoint 2010 中访问数据Note:

    注意: 在 SharePoint 2010 的 RTM 版本中,LINQ to SharePoint 不支持匿名访问。但是,2010 年 8 月的 SharePoint 2010 累计更新解决了这项限制。

对外部列表使用 SPQuery

若要查询外部列表,唯一支持的方法是使用 SPQuery 类。使用此方法,您可以完全按照查询一般 SharePoint 列表的方式来查询外部列表。但是,从外部列表访问数据时,还有一些其他的注意事项:

  • 不能跨外部列表进行联接,即使在 BDC 模型中定义了实体关联也是如此。
  • 可以通过向外部内容类型分配权限来指定身份验证规则。大部分 Web 服务和数据库都会实施身份验证和授权功能。您需要使用 Secure Store Service 或配置自己的安全机制,以便实施安全架构。
  • 限制机制和限值与针对一般 SharePoint 列表的限值机制和限值不同。当您查询外部列表时,将应用针对 BDC 运行时的限制设置。

若要从沙盒应用程序中访问外部数据,如果不使用完全信任代理,就必须使用外部列表。沙盒环境禁止使用 BDC 对象模型或直接访问外部系统。因此,如果要在沙盒中运行您的解决方案,那么将
SPQuery 类和 SPList 对象模型与外部列表结合使用,就是访问外部数据的唯一选择。

在 SharePoint 2010 中访问数据Note:

注意: 出于安全考虑,沙盒工作进程中移除了当前用户的标识令牌。若要从沙盒环境中访问外部列表,必须使用 Secure Store Service 将运行用户代码代理服务的托管帐户映射到外部系统所需的凭据。有关更多信息,请参阅 混合方法 ,该主题位于本文档的 执行模型 部分。

使用 SPSiteDataQuery

SPSiteDataQuery 类用于从网站集内不同网站中的多个列表查询数据。SPSiteDataQuery 常用于列表聚合方案。在此类方案中,需要整理来自团队网站或其他子网站的列表数据,并将其呈现到单一界面中。与
SPQuery 类不同,不能将 join 谓词或投影字段与 SPSiteDataQuery 类结合使用。SPSiteDataQuery 实例只能从 SharePoint 列表聚合数据,并且会忽略来自外部列表的数据。

使用 SPSiteDataQuery 类时的大致流程如下:

  • 创建一个 SPSiteDataQuery 实例。
  • 设置 SPSiteDataQuery 实例上的属性,以便指定要包括在查询中的列表或列表类型,以及 CAML 查询本身。
  • SPWeb 实例上调用 GetSiteData 方法,将 SPSiteDataQuery 实例作为参数传入。GetSiteData 方法将返回一个
    DataTable

下面的代码示例阐释了这一点。在本例中,工作说明 (SOW) 存储在网站集内的文档库中。该查询检索网站集内每个工作说明的 SOW 状态估计值 字段,并将结果按估计值 字段排序。


  1. SPSiteDataQuery query = new SPSiteDataQuery();
  2. query.Lists = "<Lists BaseType='1' />";
  3. query.ViewFields = "<FieldRef Name='SOWStatus' />" +
  4. "<FieldRef Name='EstimateValue' />";
  5. query.Query = "<OrderBy>
  6. <FieldRef Name='EstimateValue' />
  7. </OrderBy>";
  8. query.Webs = "<Webs Scope='SiteCollection' />";
  9. SPWeb web = SPContext.Current.Web;
  10. DataTable results = web.GetSiteData(query);

就效率而言,在以下方案中,SPSiteDataQuery 类可以提供最佳的数据访问方法:

  • 当您需要从同一网站集内的多个列表中查询相同内容时
  • 当您需要跨越未通过查找列建立关系的两个或更多列表进行查询时

您应该避免使用 LINQ to SharePoint 跨网站聚合列表数据。LINQ to SharePoint 的设计目的是跨越由查找列定义的列表关系来聚合数据。试图在 LINQ to SharePoint 中执行跨网站操作,通常需要在内存中执行查询后联接操作,而该操作需要占用大量资源。相反,SPSiteDataQuery 类针对在网站集内的多个网站中查询列表数据以及在单一网站内跨多个列表查询列表数据进行了优化。

在 SharePoint 2010 中访问数据Note:

注意: SharePoint Foundation 2010 中可以使用 SPSiteDataQuery 类。SharePoint Server 2010 包含更多适用于特定列表聚合方案的内置组件。这些组件包括内容查询 Web 组件和门户网站地图导航提供程序。

在 SharePoint 2010 中访问数据Note:

注意: 由于 SharePoint 2010 中的一个错误,如果您在某个网站上执行 SPSiteDataQuery,并且该网站包含的某个外部列表具有名为
Id 的列,就会引发 SPException (hr=0x80004005)。此问题可能会在未来的 Service Pack 或累计更新中修正。

使用 LINQ to SharePoint

LINQ to SharePoint 提供程序是 SharePoint 2010 中的一项新功能。借助此项功能,您可以使用强类型的实体模型和 LINQ 查询语法来查询 SharePoint 列表数据。实际上,LINQ to SharePoint 向开发人员隐藏了开发 CAML 查询的复杂性,有助于缩短开发时间并使代码更容易读懂。LINQ to SharePoint 提供程序在运行时将 LINQ 表达式转换为 CAML 查询。

在您自己的解决方案中使用 LINQ to SharePoint,需要遵循三个主要步骤:

  • 生成实体类。在开始针对 SharePoint 列表编写 LINQ 查询之前,必须先创建或生成强类型的实体类,用来表示您的列表数据和查找列关系。
  • 开发解决方案。向 Microsoft Visual Studio® 2010 项目中添加实体类之后,就可以针对表示数据模型的强类型实体编写 LINQ 查询。
  • 运行解决方案。在运行时,LINQ to SharePoint 提供程序将 LINQ 表达式动态转换为 CAML 查询,执行 CAML,然后将返回的项映射到强类型的数据实体。

LINQ to SharePoint 流程

尽管您可以手动开发实体类,但在大多数情况下,都需要使用 SPMetal 命令行工具。SharePoint Foundation 2010 中提供了此工具,您可以在 SharePoint 根目录下的 BIN 文件夹中找到它。SPMetal 工具针对单独的 SharePoint 网站,并且在默认情况下,会生成以下代码资源:

  • 从 DataContext 派生的数据上下文类。这是*实体类。它表示网站的内容,并且提供了一些方法用于检索列表实体。数据上下文类使用
    EntityList<TEntity> 类来表示网站中的列表,其中的 TEntity 是一个表示内容类型的类。
  • 表示内容类型的类。这些类标有 ContentTypeAttribute。隐式内容类型以及网站上显式定义的内容类型都会生成相应的内容类型类。例如,如果用户向现有列表中添加一列,该用户就创建了一个隐式内容类型,并且会生成相应的表示类。
  • 表示列表之间关系的类和属性。SPMetal 可根据查找列来检测关系。在表示内容类型的实体类中,SPMetal 使用
    EntityRef<TEntity>
    类来表示一对多关系的单一实例方,使用 EntitySet<TEntity> 类来表示一对多关系或多对多关系的“多”实例方(称为反向查找)。映射到相关列表中的某个字段的属性将用
    AssociationAttribute 进行修饰。

在 SharePoint 2010 中访问数据Note:

注意: 您可以通过创建参数文件来配置 SPMetal 工具,以便为特定的列表生成实体类,而不是为网站中的所有内容生成实体类。有关详细信息的链接,请参阅本章最后的“更多信息”部分。

生成实体类之后,就可以对强类型的实体编写 LINQ 查询,而不是创建 CAML 查询。LINQ to SharePoint 提供程序将在运行时于后台将 LINQ 查询转换为 CAML,并对 SharePoint 列表执行 CAML。为了演示如何使用 LINQ 语法来查询实体类,请考虑为制造团队创建一个 SharePoint 网站,其中包括一个名为
Priority Projects 的列表。此列表包含各种列,例如 TitleExecutive Sponsor;还包含一个查找列,用于将每个列表项链接到集中的
Projects 列表。假设您想要检索由 David Pelton 发起的所有优先项目。在每种情况下,您都想要从 Priority Projects 列表中检索
TitleExecutive Sponsor 字段,从 Projects 列表中的相关项检索
Leader
字段。您的代码将与以下代码类似:


  1. using (ManufacturingSiteDataContext context = new
  2. ManufacturingSiteDataContext(SPContext.Current.Web.Url))
  3. {
  4. string sponsor = "David Pelton";
  5. var results = from projectItem in context.PriorityProjects
  6. where projectItem.ExecutiveSponsor == sponsor
  7. select projectItem;
  8. foreach (var proj in results)
  9. {
  10. output.AppendFormat("Title: {0}  Sponsor: {1}  Leader: {2} \n",
  11. proj.Title, proj.ExecutiveSponsor, proj.Project.Leader);
  12. }
  13. }

本例中的所有实体类都是由 SPMetal 工具生成的。该示例演示了以下要点:

  • 查询使用数据上下文类。ManufacturingSiteDataContext 类继承自 DataContext 类,它包含针对制造网站上每个列表的强类型属性,例如
    Priority Projects 列表。
  • 表示列表内实体的内容类型类包含针对每个列值的强类型属性,例如 TitleExecutiveSponsor
  • 实体类了解由查找列定义的关系:Project.Leader 属性从相关的 Project 实体中检索
    Leader 字段值。

请注意,每次用完数据上下文实例后均应释放该实例。DataContext 基类实现了 IDisposable 接口,因此可确保在执行超出
using 语句的范围时释放数据上下文实例。

SharePoint 2010 的产品文档提供了有关如何使用 LINQ to SharePoint 的全面基本信息,您可以在本章最后的“更多信息”部分中找到指向相关资料的链接。本部分重点介绍 LINQ to SharePoint 作为数据访问策略的功能、关键问题和限制,旨在帮助您评估 LINQ to SharePoint 是否适合您的特定应用程序方案。

LINQ to SharePoint 查询是如何执行的?

LINQ to SharePoint 对结果集使用推迟加载方法(通常称为“延迟加载”)来提高查询效率。如果您创建一个查询以便返回一组实体,则在您开始执行需要使用其结果集的操作(例如循环访问结果或将结果集转换为数组)之前,该查询不会真正执行。在上述代码示例中,仅当
foreach 语句开始枚举结果集时,LINQ 查询才会转换为 CAML 并得以执行。

LINQ to SharePoint 还对相关实体使用推迟加载方法。仅当真正访问实体时,才会加载所有相关实体,这样就可以减少对内容数据库的不必要调用。在上述代码示例中,仅当
foreach 语句读取 Project.Leader 属性时,才会加载 Project 实体。

在 HTTP 请求的上下文中执行某个查询时,LINQ to SharePoint 使用 SPContext.Current 属性来加载数据上下文。这使得加载数据上下文的过程相对高效。但是,如果您在 HTTP 请求的上下文以外使用 LINQ 查询,例如在命令行应用程序中或者在 Windows® PowerShell ™ 脚本中,LINQ to SharePoint 提供程序就必须构造上下文对象,例如
SPWebSPSite,以便构建数据上下文实例。在这种情况下,此过程就需要占用更多资源。在 LINQ 查询中执行的任何创建更新删除操作,都会由数据上下文实例自动批量处理,并且会在您的代码调用
DataContext.SubmitChanges 方法时应用。

为内容类型生成实体

SPMetal 命令行工具可以为 SharePoint 网站中定义的内容类型生成实体类。内容类型有许多特征,会使此过程难以理解:

  • 内容类型支持继承。
  • 可以在网站级或列表级定义内容类型。向列表添加内容类型时,SharePoint 会创建内容类型的本地副本,该副本可供修改。
  • 一个列表可以关联多个内容类型。

SPMetal 在生成内容类型时遵循以下规则:

  • 为网站上的每个内容类型生成一个实体类 (SPWeb)。
  • 如果内容类型继承自其他内容类型,则表示子内容类型的实体类也将继承自表示父内容类型的实体类。例如,在 沙盒参考实现中,SOW(工作说明)内容类型继承自内置的
    Document 内容类型,而后者又继承自内置的 Item 内容类型。SPMetal 将为
    SOW
    DocumentItem 生成实体类,并且会在这些类之间构建继承关系。
  • 如果从相应的网站内容类型中修改了列表内容类型,SPMetal 将为列表内容类型生成新的实体类。如果列表内容类型与相应的网站内容类型相同,SPMetal 将直接使用该网站内容类型的实体类。从列表内容类型创建的实体的命名方式是在内容类型的名称之前加上列表名称。例如,如果您向 Estimates 列表中的
    SOW 内容类型添加一个 StartDate 列,将生成一个名为 EstimatesSOW 的实体类,用来表示该列表内容类型。相反,如果您没有在 Estimates 列表中修改
    SOW 内容类型,将生成一个名为 SOW 的实体类,用来表示该网站内容类型。
  • 如果从列表内容类型移除一个列,就会在表示网站内容类型的实体类中将相应的属性设置为 virtual。表示列表内容类型的实体类将重写此方法;如果您试图访问该属性,该实体类会引发
    InvalidOperationException。例如,如果您从 Estimates 列表中的 SOW 内容类型移除 VendorID 列,就会在
    SOW 实体类中将 VendorID 属性设置为 virtual;如果您试图访问该属性,EstimatesSOW 实体将引发异常。
  • 如果列表仅包含一种内容类型,在数据上下文类中表示该列表的 EntityList<TEntity> 类将使用该内容类型实体作为其类型参数。例如,如果 Estimates 列表仅包含基于
    SOW 内容类型的文档,该列表将表示为 EntityList<SOW> 实例。
  • 如果列表包含多种内容类型,则表示该列表的 EntityList<TEntity> 类将使用最接近的基内容类型作为其类型参数。例如,Estimates 列表实际包含
    SOW 内容类型和 Estimate 内容类型,这两种类型均继承自内置的 Document 内容类型。在这种情况下,该列表将表示为
    EntityList<Document> 实例。因为 SOW 实体和 Estimate 实体均继承自
    Document 实体,所以该列表就可以包含这两种类型的实体。

在实体类中为关联建模

当您使用 SPMetal 命令行工具来生成实体类时,它会根据查找列自动检测列表之间的关系,并且会向实体类添加属性,以便您导航这些关系。请考虑一个库存管理网站,该网站包含一个
Parts 列表和一个 Inventory Location 列表。Inventory Location 列表包含一个名为
Part 的查找列,该查找列从 Parts 列表中检索值。当您生成相应的实体类时,InventoryLocation 类将包括一个
Part 属性,该属性使您能够导航到 Parts 列表中的关联实体实例。


  1. private Microsoft.SharePoint.Linq.EntityRef<Part> _part;
  2. [Microsoft.SharePoint.Linq.AssociationAttribute(Name="PartLookup",
  3. Storage="_part",
  4. MultivalueType=Microsoft.SharePoint.Linq.AssociationType.Single,
  5. List="Parts")]
  6. public Part Part
  7. {
  8. get { return this._part.GetEntity(); }
  9. set { this._part.SetEntity(value); }
  10. }

在 SharePoint 2010 中访问数据Note:

注意: 这些示例来自《开发 SharePoint 2010 应用程序》联机指南中的“SharePoint 列表数据模型参考实现”。InventoryLocation 类还包括一些事件处理程序;当关联的实体实例被更改时,这些事件处理程序可确保
Part 引用保持最新。

SPMetal 还会向 Parts 列表添加属性,使您能够导航到 Inventory Locations 列表。这被称为反向查找关联Parts 类包含一个
InventoryLocation 属性,该属性返回一组与特定零件相关联的库存位置,也就是说,通过 Part 查找列链接到指定零件的每个
InventoryLocation 实例。


  1. private Microsoft.SharePoint.Linq.EntitySet<InventoryLocation> _inventoryLocation;
  2. [Microsoft.SharePoint.Linq.AssociationAttribute(Name="PartLookup",
  3. Storage="_inventoryLocation", ReadOnly=true,
  4. MultivalueType=Microsoft.SharePoint.Linq.AssociationType.Backward,
  5. List="Inventory Locations")]
  6. public Microsoft.SharePoint.Linq.EntitySet<InventoryLocation> InventoryLocation
  7. {
  8. get { return this._inventoryLocation; }
  9. set { this._inventoryLocation.Assign(value); }
  10. }

在 SharePoint 2010 中访问数据Note:

注意:Part 类还包括一些事件处理程序;当关联的实体实例被更改时,这些事件处理程序可确保 InventoryLocation 引用保持最新。

但是,当前版本的 SPMetal 在构建反向查找时,存在一些限制:

  • 如果网站查找列由一个列表使用,SPMetal 将为此关系生成反向查找关联。
  • 如果网站查找列由多个列表使用,则 SPMetal 不会为任何基于该查找列的关系生成反向查找关联。

在很多方案中,您都需要在多个列表中使用查找列。例如,在参考实现中,有三个列表使用查找列从 Parts 列表检索值。有些情况下,根据您打算用来查询数据的方式,可能不需要反向查找关联。但是当您需要反向遍历此关系时,如果在不具备反向查找关联的情况下继续操作,您的 LINQ to SharePoint 查询的效率就会低很多。考虑 Parts 和 Inventory Locations 之间的关系。如果您需要查找与指定零件相关联的所有库存位置,就需要先检索该零件,然后在库存位置实体中查询所有相关位置,在这些位置中,零件的查找列与
Part 项的 ID 值相等。在这种情况下,反向查找关联可以简化 LINQ 表达式,并减少处理开销。

您可以使用许多方法来解决 SPMetal 的这项限制,但每种方法都有其缺陷:

  1. 如果某个列表需要针对特定列表的查找列,则为该列表创建新的站点列。这会导致多个网站列从同一个列表检索信息,这些列除了名称以外完全相同。这会产生一些负面的后果:
    • 如果开发人员使用已经在使用中的网站查找列,则在您下次使用 SPMetal 时,不会为该列生成反向查找,并且某些现有代码将无法正常运行。
    • 网站管理员需要为同一个值管理多个网站列,这将令人困惑。通过隐藏重复的查找字段,可以减轻此缺陷。
    • 网站列并不真的可重用,这是首先使用网站列的主要目的。
  2. 在列表级创建查找列。这会消除与重复的网站列相关的问题。但是这样做会有以下负面后果:     
    • 您的内容类型不再表示您的数据模型,因为查找列现在被推送到各个列表中。这让信息管理变得更为困难。它还会降低需要从不同列表中检索项的搜索和查询的效率,因为来自查找列的信息不会包含在内容类型中。
  3. 创建重复的网站列,并且在内容类型或列表定义中使用它们,以便利用 SPMetal 来生成实体类,如同方式 1 一样。在生成实体类之后,删除重复的网站查找列,并且手动编辑实体类,以便使用单一查找列。这会使您的数据模型保持干净利落,因为您不需要维护重复的网站列;并且它可以避免与方式 2 相关的问题,因为该查找列包含在相关的内容类型中。在大多数方案中,这都是首选方式。但是,这样做会有以下负面后果:     
    • 要创建重复的网站列,创建内容类型定义,删除重复的网站列,然后编辑实体类,需要付出更多努力。
    • 手动编辑实体类,很容易产生错误并且很难调试。但是,此编辑工作应该仅需要简单地重命名属性。
  4. 如果有多个列表或内容类型使用特定的网站查找列,请避免使用反向查找关联。尽管此方法很简单,但是如果您需要在没有相反查找属性的情况下按相反方向导航关联,就需要使用更复杂、效率也较低的 LINQ 查询。

LINQ to SharePoint 的查询效率

尽管 LINQ to SharePoint 使得查询 SharePoint 列表变得简单方便,您仍然需要考虑您的 LINQ 表达式是否会转换成高效的 CAML 查询。如果 LINQ 代码转换成高效的 CAML 查询,则除了某些极端情况以外,就可以忽略 LINQ to SharePoint 提供程序的性能开销。事实上,LINQ to SharePoint 所能实现的性能可能会更高,因为很难手动创建高效的 CAML 查询。本部分说明 LINQ 表达式中的细微差别如何会对生成的查询造成重大影响。

在有些情况下,LINQ to SharePoint 会禁止执行某些低效率的查询。LINQ to SharePoint 提供程序并不总是能将 LINQ 表达式转换成单一 CAML 查询。例如,如果您使用 join 谓词在两个列表中进行查询,并且这两个列表未通过查找列连接起来,则 LINQ to SharePoint 提供程序实际上需要提交两个查询以便返回结果集。如果在与此类似的情况下,LINQ to SharePoint 无法使用一个 CAML 查询来执行操作,运行时将引发
NotSupportedException。在其他情况下,LINQ to SharePoint 提供程序有可能无法将整个 LINQ 代码转换为高效的 CAML 查询。在这些情况下,提供程序将先执行一个 CAML 查询,以便从列表中查询项;然后对列表项集合结果执行
LINQ to Objects 查询,以便完成不能转换为 CAML 的那部分 LINQ 查询。

例如,假设您想要查看每位客户的订单。您可能使用以下 LINQ 表达式。


  1. dataContext.Customers.Select(c=>c.Orders).ToArray();

在本例中,LINQ to SharePoint 提供程序需要为每个客户再提交一个查询,以便检索其订单。因此,运行时将引发异常。同样,假设您希望聚合来自两个不同客户列表的数据。您可能使用以下 LINQ 表达式。


  1. dataContext.Customers.Union(dataContext.MoreCustomers).ToArray();

在本例中,LINQ to SharePoint 提供程序需要提交两个查询,每个列表一个查询。运行时同样会引发异常。本部分的剩余篇幅将为您介绍可用来执行此类查询的方法以及其他不会损害效率的常见操作。

查看 CAML 输出

在很多情况下,查看 LINQ 查询所生成的 CAML 输出可能会对您有所帮助。DataContext 类包括一个
Log
属性,该属性公开一个 TextWriter 对象。您可以使用此属性,将所生成的 CAML 查询记录到文本文件或用户界面中。例如,以下代码说明如何修改上一个示例,以便查看所生成的 CAML 查询。在本例中,CAML 查询被附加到名为
displayAreaLiteral 控件中的查询结果之后。


  1. using (ManufacturingSiteDataContext context = new
  2. ManufacturingSiteDataContext(SPContext.Current.Web.Url))
  3. {
  4. var sb = new StringBuilder();
  5. var writer = new StringWriter(sb);
  6. context.Log = writer;
  7. string sponsor = "David Pelton";
  8. var results = from projectItem in context.PriorityProjects
  9. where projectItem.ExecutiveSponsor == sponsor
  10. select projectItem;
  11. foreach (var proj in results)
  12. {
  13. output.AppendFormat("Title: {0}  Sponsor: {1}  Leader: {2}",
  14. proj.Title, proj.ExecutiveSponsor, proj.ProjectsLookup.Leader);
  15. }
  16. output.Append("\n Query: " + sb.ToString());
  17. displayArea.Mode = LiteralMode.Encode;
  18. displayArea.Text = output.ToString();
  19. }

Log 属性设置为一个 TextWriter 实现后,DataContext 类将在执行 LINQ 表达式时,把 CAML 查询写入到基础流或字符串中。然后,您就可以查看由 LINQ to SharePoint 提供程序生成的 CAML 查询。

<View>
<Query>
<Where>
<And>
<BeginsWith>
<FieldRef Name="ContentTypeId" />
<Value Type="ContentTypeId">0x0100</Value>
</BeginsWith>
<Eq>
<FieldRef Name="Executive_x0020_Sponsor" />
<Value Type="Text">David Pelton</Value>
</Eq>
</And>
</Where>
</Query>
<ViewFields>
<FieldRef Name="Executive_x0020_Sponsor" />
<FieldRef Name="ProjectsLookup" LookupId="TRUE" />
<FieldRef Name="ID" />
<FieldRef Name="owshiddenversion" />
<FieldRef Name="FileDirRef" />
<FieldRef Name="Title" />
</ViewFields>
<RowLimit Paged="TRUE">2147483647</RowLimit>
</View>

对于自动生成的 CAML 查询,有一些有趣的发现:

  • 请注意 Where 子句中的 BeginsWith 元素。它规定所返回项的内容类型 ID 必须以
    0x0100 开头。实际上,这意味着所返回项的内容类型必须是继承自内置 Item 内容类型的自定义内容类型,Project 内容类型就是这样一种内容类型。除了 LINQ 查询指定的
    where 子句以外,LINQ to SharePoint 提供程序还会包括此配置。
  • CAML 查询返回一个视图,该视图包含 Priority Projects 列表中的所有字段,包括 LINQ 表达式并不需要的字段。
  • 该查询为 Projects 列表返回一个查找字段,而不是一个实体。LookupId 属性表明
    Projects
    列表中的被引用项将通过其内部 ID 值进行检索。

在开发过程中,您应该投入时间来检查 LINQ 查询所生成的 CAML,以便主动辨别效率不佳的查询。当您对可能很大的列表进行查询时,这尤其重要。例如,当 LINQ to SharePoint 提供程序无法将部分或全部查询转换为 CAML 并且必须求助于 LINQ to Objects 时,您就应该注意到这种明显的问题。

Where 子句的效率

在创建 LINQ 表达式时,通常是使用 where 子句中的运算符来构造结果集。但是,LINQ to SharePoint 提供程序无法将每个 LINQ 运算符都转换为 CAML。例如,Equals 运算符和
HasValue 运算符就没有对等的 CAML 内容。LINQ to SharePoint 提供程序会尽量将 where 子句运算符转换为 CAML,然后使用 LINQ to Objects 来满足剩下的条件。

下表显示了 LINQ to SharePoint 提供程序支持的运算符以及它们在 CAML 中的对等表达式。

LINQ 运算符

CAML 转换

&&

And

||

Or

==

Eq

>=

Geq

>

Gt

<=

Leq

<

Lt

!=

Neq

== null

IsNull

!= null

IsNotNull

String.Contains

Contains

String.StartsWith

BeginsWith

您应该避免在 LINQ to SharePoint 查询中使用此表中未列出的运算符。使用不受支持的运算符会导致 LINQ to SharePoint 提供程序返回更大的结果集,然后需要在客户端使用 LINQ to Objects 来处理未完成的
where 子句。这会产生可观的性能开销。例如,考虑下面的 LINQ 表达式。其 where 子句包括一个
Equals 运算符和一个 StartsWith 运算符。


  1. var results = from projectItem in context.PriorityProjects
  2. where projectItem.ExecutiveSponsor.Equals(sponsor)  &&
  3. projectItem.Title.StartsWith("Over")
  4. select projectItem;

所产生的 CAML 查询包括一个 Where 子句,该子句反映了 StartsWith 运算符。但是,它没有提到不受支持的
Equals 运算符。

<View>
<Query>
<Where>
<And>
<BeginsWith>
<FieldRef Name="ContentTypeId" />
<Value Type="ContentTypeId">0x0100</Value>
</BeginsWith>
<BeginsWith>
<FieldRef Name="Title" />
<Value Type="Text">Over</Value>
</BeginsWith>
</And>
</Where>
</Query>
<ViewFields>

</ViewFields>
<RowLimit Paged="TRUE">2147483647</RowLimit>
</View>

在本例中,LINQ to SharePoint 提供程序将返回一个结果集,其中包括的项目项的 Title 字段以“Over”开头,如 CAML 查询所定义。然后,它将在客户端使用 LINQ to Objects 从结果集中查询具有匹配
ExecutiveSponsor 字段的项目项,如不受支持的 Equals 运算符所定义。

以下 XML 显示了如果您改写该 LINQ 表达式,使用受支持的 == 运算符来代替不受支持的 Equals 运算符,所产生的结果。

var results = from projectItem in context.PriorityProjects
where projectItem.ExecutiveSponsor == sponsor &&
projectItem.Title.StartsWith("Over")
select projectItem;

这一次,所产生的 CAML 查询将完全反映该 LINQ 表达式。

<View>
<Query>
<Where>
<And>
<BeginsWith>
<FieldRef Name="ContentTypeId" />
<Value Type="ContentTypeId">0x0100</Value>
</BeginsWith>
<And>
<Eq>
<FieldRef Name="Executive_x0020_Sponsor" />
<Value Type="Text">David Pelton</Value>
</Eq>
<BeginsWith>
<FieldRef Name="Title" />
<Value Type="Text">Over</Value>
</BeginsWith>
</And>
</And>
</Where>
</Query>
<ViewFields>

</ViewFields>

在本例中,LINQ to SharePoint 提供程序仅向客户端返回相关的结果,也不需要执行后期处理步骤。

使用视图投影

在许多情况下,您可以通过使用视图投影 显著提高查询效率。视图投影可以从一个或多个实体查询一组特定的字段。当您需要检索一组数据的只读视图时,使用视图投影可限制由查询返回的字段数量,并确保联接操作被添加到 CAML 查询中,而不是作为后期处理步骤执行。有多种方法可以创建视图投影:

  • 选择单一字段,例如 projectItem.Title
  • 通过从一个或多个实体选择一组特定的字段来构建匿名类型。
  • 实例化某种已知类型,并在 LINQ 表达式中设置属性值。

视图投影仅限于部分字段类型。有效的投影字段类型包括 Text(仅限单行文本)、DateTimeCounter(内部 ID)、Number
ContentTypeId。其他所有字段类型都不受支持;如果在投影中使用该字段类型的列,将引发 InvalidOperationException

在以下示例中,LINQ 表达式中的 new 关键字会创建一个匿名类型,其中包含 TitleExecutiveSponsor
Leader 字段。


  1. using (ManufacturingSiteDataContext context = new ManufacturingSiteDataContext(SPContext.Current.Web.Url))
  2. {
  3. string sponsor = "David Pelton";
  4. var results = from projectItem in context.PriorityProjects
  5. where projectItem.ExecutiveSponsor == sponsor
  6. select new { projectItem.Title,
  7. projectItem.ExecutiveSponsor,
  8. projectItem.Project.Leader };
  9. foreach (var proj in results)
  10. {
  11. output.AppendFormat("Title: {0}  Sponsor: {1}  Leader: {2}",
  12. proj.Title, proj.ExecutiveSponsor, proj.Leader);
  13. }
  14. }

在本例中,LINQ to SharePoint 提供程序会创建一个视图,其中仅包含与匿名类型中的字段相对应的列。

<View>
<Query>
<Where>
<And>
<BeginsWith>
<FieldRef Name="ContentTypeId" />
<Value Type="ContentTypeId">0x0100</Value>
</BeginsWith>
<Eq>
<FieldRef Name="Executive_x0020_Sponsor" />
<Value Type="Text">David Pelton</Value>
</Eq>
</And>
</Where>
</Query>
<ViewFields>
<FieldRef Name="Title" />
<FieldRef Name="Executive_x0020_Sponsor" />
<FieldRef Name="ProjectLeader" />
</ViewFields>
<ProjectedFields>
<Field Name="ProjectLeader" Type="Lookup" List="Project" ShowField="Leader" />
</ProjectedFields>
<Joins>
<Join Type="LEFT" ListAlias="Project">
<!--List Name: Projects-->
<Eq>
<FieldRef Name="Project" RefType="ID" />
<FieldRef List="Project" Name="ID" />
</Eq>
</Join>
</Joins>
<RowLimit Paged="TRUE">2147483647</RowLimit>
</View>

替代方法是实例化一个已知类型并在 LINQ 表达式中设置属性值,如以下示例所示。


  1. public class PriorityProjectView
  2. {
  3. public string Title { get; set; }
  4. public string ExecutiveSponsor { get; set; }
  5. public string Leader { get; set; }
  6. }
  7. using (ManufacturingSiteDataContext context = new
  8. ManufacturingSiteDataContext(SPContext.Current.Web.Url))
  9. {
  10. IEnumerable<PriorityProjectView> proirityProjects =
  11. from projectItem in context.PriorityProjects
  12. where projectItem.ExecutiveSponsor == sponsor
  13. select new PriorityProjectView
  14. {
  15. Title = projectItem.Title,
  16. ExecutiveSponsor = projectItem.ExecutiveSponsor,
  17. Leader = projectItem.Project.Leader
  18. };
  19. }
  20. ...

仅检索实际需要的列,可以明显提高查询的效率;就这一点而言,使用视图投影可以显著提高性能。本示例还演示了如何使用视图投影强制 LINQ to SharePoint 提供程序在 CAML 查询中执行列表联接,而不是如上文所述,检索查找列并使用推迟加载方法。当您使用视图投影时,LINQ to SharePoint 提供程序只会生成 CAML 联接。如果您事先知道要显示来自两个或更多实体的数据,这种方法就更有效,因为它可以减少与内容数据库之间的往返次数。

在 SharePoint 2010 中访问数据Note:

注意: 视图投影仅适用于读取操作。如果您想使用 LINQ to SharePoint 来执行创建更新删除操作,则必须检索完整的实体实例。

LINQ to SharePoint 只能从视图投影为有限数量的数据类型生成 CAML 联接。如果投影中包含不被允许的数据类型,将引发 InvalidOperationException。允许的类型包括
TextNumberDateTimeCount内容类型 ID。其余所有字段类型都不能投影,包括 Boolean、多行文本、选项、货币和计算字段。

最后,对于视图投影来说,请记住 LINQ to SharePoint 提供程序将阻止某些 LINQ 表达式,因为它们无法转换为单个 CAML 查询。例如,以下 LINQ 表达式试图检索每个客户的订单集。但是,LINQ to SharePoint 无法将该 LINQ 表达式转换为单个 CAML 查询。


  1. dataContext.Customers.Select(c=>c.Orders).ToArray();

假设您将该表达式改为使用匿名类型,如以下示例所示。


  1. var results = dataContext.Customers.Select(c => new { Description =
  2. c.Order.Description, CustomerId = c.Order.CustomerId }).ToArray();

在这种情况下,LINQ to SharePoint 提供程序就能够将该表达式转换为单个 CAML 查询,并且运行时不会引发异常。可以看到,当您开发 LINQ to SharePoint 表达式时,视图投影可以提供有价值的资源。

跨站点使用列表联接

在许多常见的 SharePoint 方案中,列表中都会包括查找列,用于从网站集内的父网站中的另一个列表检索数据。但是,SPMetal 命令行工具是为单一网站生成实体类的,而且 LINQ to SharePoint 表达式也在表示单一网站的数据上下文中执行操作。默认情况下,当列表中包括一个查找列,并且该查找列引用另一个网站上的列表时,SPMetal 将为相关列表中的项生成一个
ID 值,而不是构造相关实体本身。如果您要针对此数据模型编写查询,需要在后期处理步骤中自行检索相关实体。因此,如果您想要使用 LINQ to SharePoint 有效查询跨网站的列表关系,就必须执行更多步骤:

  1. 在运行 SPMetal 工具之前,将每个列表都临时移到同一个网站上,以便 SPMetal 生成全部实体类。
  2. 当您创建 LINQ 表达式时,使用 DataContext.RegisterList 方法向运行时告知不在当前网站上的列表的位置。

考虑上文中的 Priority Projects 列表示例。该列表中包含的查找列从父网站上的* Projects 列表中检索信息,如图 1 所示。

在 SharePoint 2010 中访问数据

图 1 跨越网站集内多个网站的查找列关系

若要使用 SPMetal 工具为两个列表生成实体,您应该在根网站上创建 Priority Projects 列表的副本,如图 2 所示。

在 SharePoint 2010 中访问数据

图 2 用于构建实体类的临时列表

SPMetal 现在将构建全部实体和实体关系。完成实体类的构建后,您可以从网站中删除重复的列表。当您在 Construction 团队网站的上下文中运行查询时,必须使用
RegisterList 方法告知运行时从何处查找 Projects 列表。下面的代码示例演示这一情况。


  1. using (ManufacturingSiteDataContext context = new ManufacturingSiteDataContext("http://localhost/sites/manufacturing/construction"))
  2. {
  3. context.RegisterList<Construction.ProjectsItem>("Projects",
  4. "/sites/Manufacturing", "Projects");
  5. var results = from projectItem in context.PriorityProjects
  6. select new { projectItem.Title,
  7. projectItem.ExecutiveSponsor,
  8. projectItem.Project.Leader };
  9. foreach (var item in results)
  10. {
  11. output.AppendFormat("Title: {0}  Sponsor: {1}  Leader: {2}",
  12. item.Title, item.ExecutiveSponsor, item.Leader);
  13. }
  14. }

有很多方法可供您选择,用来为生成实体而设置列表。在大多数情况下,由于查找列引用了某个网站上的列表,您将需要从该网站生成实体类。也就是说,如果查找列从根网站上的列表中检索数据,您应该将所有列表都移到根网站上,并从根网站构建实体模型。如果您构建了一个实体模型,该模型使用查找列从某个网站上的列表中检索数据,然后又将该列表移到另一个网站上,则需要手动更新实体类。

用于构建实体模型的主要方法如下:

  • 在根网站上创建所有列表的副本,并使用 SPMetal 从根网站构建单个全面的实体模型。对于大多数方案,推荐使用此方法,因为它通常最简单,不需要在创建后修改实体类。
  • 为 SPMetal 提供参数文件,以便为某个具体的实体关系构建专门的实体模型。例如,假设您有一个查找列,该列从某个团队网站,而不是从根网站检索数据。在这种情况下,您应该考虑将所有相关列表复制到该团队网站上,并从该网站构建实体模型,这样就无需手动编辑实体类中的查找关系。如果您的网站集中有大量列表,也可以考虑此方法,因为不值得投入精力在根网站上复制和维护每一个列表。
  • 当某个子网站上有一个列表包含一个查找列,该查找列从根网站上的列表检索值时,您可能会忍不住将根网站上的列表复制到子网站上,然后在子网站上生成实体类。但是,一般情况下应该避免使用这种方法。首先,您需要生成临时查找列,因为您要使用的实际查找列与根网站上的特定列实例相关联。其次,您需要手动编辑实体类中的关联,以便使用实际的查找列,而不是临时查找列。

最后,请记住 SPQuery 类支持基于 CAML 的列表联接。LINQ to SharePoint 的主要设计目标是加快开发过程。如果设置和维护列表副本以便构建有代表性的实体模型所需的时间超过了您由于编写 LINQ 表达式而不是 CAML 查询而节省的时间,则您需要考虑
SPQuery 对您的应用程序方案来说是不是更好的选择。

其他性能注意事项

LINQ 表达式定义了一个泛型 IEnumerable<T> 对象集合。Enumerable 类提供了一组扩展方法,您可以使用这些方法来查询和操作此集合。当您在 LINQ to SharePoint 表达式中使用这些方法时,它们的效率会有所变化。下表简要描述了尚未介绍的一些操作的某些性能问题。标为“高效”的操作会被转换为 CAML,并且在检索列表数据后不需要执行后期处理步骤。

操作

性能和行为

Contains

高效

OrderBy

高效

OrderByDescending

高效

ThenBy

高效

ThenByDescending

高效

GroupBy

如果与 OrderBy 结合使用,则为高效

Sum

返回满足 where 子句的所有元素,然后使用 LINQ to Objects 来计算元素之和

Aggregate

返回满足 where 子句的所有元素,然后使用 LINQ to Objects 向元素应用累加器功能

Average

返回满足 where 子句的所有元素,然后使用 LINQ to Objects 来计算平均值

最大值

返回满足 where 子句的所有元素,然后使用 LINQ to Objects 来计算最大值

最小值

返回满足 where 子句的所有元素,然后使用 LINQ to Objects 来计算最小值

Skip

返回满足 where 子句的所有元素,然后使用 LINQ to Objects 来执行 Skip 操作

SkipWhile

返回满足 where 子句的所有元素,然后使用 LINQ to Objects 来执行 SkipWhile 操作

ElementAt

不受支持;请使用 Take 方法

ElementAtOrDefault

不受支持;请使用 Take 方法

Last

返回满足 where 子句的所有项,然后获取最后一个项

LastOrDefault

返回满足 where 子句的所有项,然后获取最后一个项;如果未发现这样的项,则返回默认值

全部

返回满足 where 子句的所有元素,然后使用 LINQ to Objects 来计算条件的值

任意

返回满足 where 子句的所有元素,然后使用 LINQ to Objects 来计算条件的值

AsQueryable

高效

Cast

高效

Concat

高效

DefaultIfEmpty

高效

Distinct

跨越两个集合执行;返回满足 where 子句的所有元素,然后使用 LINQ to Objects 筛选掉重复项

Except

跨越两个集合执行;返回满足 where 子句的所有元素,然后使用 LINQ to Objects 来计算差集

First

高效

FirstOrDefault

高效

GroupJoin

高效

Intersect

跨越两个集合执行;返回满足 where 子句的所有元素,然后使用 LINQ to Objects 来计算交集

OfType

高效

Reverse

返回满足 where 子句的所有元素,然后使用 LINQ to Objects 来反转序列的顺序

SelectMany

高效

SequenceEqual

跨越两个集合执行;返回满足 where 子句的所有元素,然后使用 LINQ to Objects 来计算两个集合是否相等

Single

高效

SingleOrDefault

高效

Take

高效

TakeWhile

高效

Union

高效

存储库模式和 LINQ to SharePoint

存储库模式 是一种应用程序设计模式,它提供了集中的独立数据访问层。存储库在基础数据源中检索和更新数据,并将该数据映射到实体模型。使用此方法,可以将数据访问逻辑与业务逻辑分开。

从某些方面看来,有了 LINQ to SharePoint,似乎就不再需要存储库。但是,有许多理由支持您继续将 LINQ to SharePoint 与存储库模式结合使用:

  • 查询优化。与直接创建 CAML 查询相比,LINQ to SharePoint 确实可以大大减少查询开发工作量。但是,就像本章上文中所述,编写的 LINQ to SharePoint 查询很可能效率很低。在集中存储库中开发查询,意味着需要优化的查询更少,而且遇到问题时也只需要查看一个位置。
  • 可维护性。如果您直接在业务逻辑中使用 LINQ to SharePoint,则当数据模型改变时,就需要在多个位置更新代码。存储库模式将数据的使用者与数据的提供者分离,因此在数据模型改变时,只需要更新查询,而不会影响整个代码中的业务逻辑。
  • 可测试性。存储库提供了一个替代点,您可以在此处插入虚设对象以便进行单元测试。
  • 灵活性。存储库模式提高了分层和分离水平,因此可产生更灵活、可重用的代码。

在实践中,您需要在存储库模式的优势与实现解决方案的可行性之间进行权衡。在《开发 SharePoint 2010 应用程序》联机指南附带的参考实现中,构建了以下实践:

  • 将所有 LINQ to SharePoint 查询封装到存储库中。这为查询提供了一个集中管理点。
  • 配置存储库类,以便返回由 SPMetal 命令行工具生成的实体类型。这样可以避免在创建自定义业务实体并将其映射到 SPMetal 实体类方面的额外开销,因此是实现存储库模式的一种纯正方法。但是,此方法也有缺陷,它会导致数据模型与数据使用者之间的耦合更加紧密。
  • 将视图对象添加到存储库中,以便返回复合的实体投影。视图对象组合了来自多个实体的字段,而且使用视图投影可以使跨多个实体的 LINQ to SharePoint 查询的效率更高,如本主题上文所述。即使此方法偏离了存储库模式,它还是用在参考实现中,因为视图相对简单,并且视图中表示的实体均从属于同一个存储库。如果视图更复杂,或者涉及的实体跨越了多个存储库,开发人员就需要实现单独的类来管理视图,以便实现更清晰的职责划分。

使用 BDC 对象模型

从简单的显示来自外部数据源的信息列表,到提供全面自定义的交互活动,用于访问外部数据的各种方案的复杂程度差别很大。对于许多基本方案,使用内置的业务数据 Web 部件或使用
SPList API 来查询外部列表,可提供简单有效的方法来满足您的应用程序需求。但是,对于更复杂的方案,您需要使用 BDC 对象模型,以便全面控制与外部数据实体的交互方式。

BDC 对象模型不能用于沙盒解决方案。下表总结了外部列表或业务数据 Web 部件能够满足您的需求的方案以及必须使用 BDC 对象模型的方案。

外部数据方案

外部列表

业务数据 Web 部件

BCS API

从沙盒解决方案中访问二维(平面)数据

在 SharePoint 2010 中访问数据

从场解决方案中访问二维(平面)数据

在 SharePoint 2010 中访问数据

在 SharePoint 2010 中访问数据

在 SharePoint 2010 中访问数据

访问具有非整数标识符的数据

在 SharePoint 2010 中访问数据

在 SharePoint 2010 中访问数据

在 SharePoint 2010 中访问数据

导航实体之间的一对一关联

在 SharePoint 2010 中访问数据

在 SharePoint 2010 中访问数据

导航实体之间的一对多关联

在 SharePoint 2010 中访问数据

在 SharePoint 2010 中访问数据

导航实体之间的多对多关联

在 SharePoint 2010 中访问数据

读取复杂类型的实体,该类型可以使用格式字符串展平

在 SharePoint 2010 中访问数据

在 SharePoint 2010 中访问数据

在 SharePoint 2010 中访问数据

读取复杂类型的实体,该类型不能使用格式字符串展平

在 SharePoint 2010 中访问数据

创建、更新或删除复杂类型的实体

在 SharePoint 2010 中访问数据

对数据执行分页或分块

在 SharePoint 2010 中访问数据

流式二进制对象

在 SharePoint 2010 中访问数据

从客户端逻辑中访问二维(平面)数据*

在 SharePoint 2010 中访问数据

在 SharePoint 2010 中访问数据

从客户端逻辑中导航实体之间的关联*

在 SharePoint 2010 中访问数据

*BDC 提供一套客户端 API 和一套服务器端 API,两者的功能相同。但是,客户端 API 只能用于完整的 .NET 应用程序中,而不能从 Silverlight 或 JavaScript 中使用。

借助 BDC 运行时 API,您可以通过编程方式来导航 BDC 模型,或者通过该模型与外部系统交互,而无需使用中间组件,例如外部列表或业务数据 Web 部件。图 3 演示了 BDC 编程模型的关键组件。

在 SharePoint 2010 中访问数据

图 3 BDC 编程模型的关键组件

该编程模型中的每个组件均与 BDC 模型的特定部件相关联,这些部件已在上一章中进行了介绍。BDC 服务应用程序实例BdcService 类)表示为您要访问的外部系统管理元数据的服务实例。请记住,您使用的 BDC 服务应用程序实例由与当前 SharePoint Web 应用程序关联的服务应用程序代理组决定。每个 BDC 服务应用程序实例公开一个元数据目录 (IMetadataCatalog),您可以使用它来浏览该服务所存储的元数据定义。

在元数据目录中,两个主要的概念是实体 (IEntity) 和 LOB 系统实例 (ILobSystemInstance)。实体表示外部内容类型,它定义了用来与外部数据实体交互的构造型操作。它还可以定义关联(使您能够导航到相关实体)和筛选器(使您能够约束结果集)。LOB 系统实例 表示由 BDC 模型所表示的外部系统的具体实例或安装,它定义了连接到该系统时所需的连接和身份验证信息。

在 SharePoint 2010 中访问数据Note:

注意: “LOB 系统实例”是一个来自 Office® SharePoint Server 2007 的旧术语。一个 LOB 系统就是一个业务线应用程序,例如客户关系管理 (CRM) 或企业资源计划 (ERP) 软件。尽管 BDC 对象模型中仍然使用术语“LOB 系统实例”,但是其他情况下建议使用更宽泛的术语“外部系统”。

实体(或外部内容类型)对系统的所有实例通用。若要从外部系统的具体实例访问数据,需要将实体对象与 LOB 系统实例对象结合使用,以便检索实体实例 (IEntityInstance)。下面的代码示例阐释了这一点。


  1. public IEntityInstance GetMachineInstance(int machineId)
  2. {
  3. const string entityName = "Machines";
  4. const string systemName = "PartsManagement";
  5. const string nameSpace = "DataModels.ExternalData.PartsManagement";
  6. //Get the BDC service application instance.
  7. BdcService bdcService = SPFarm.Local.Services.GetValue<BdcService>();
  8. //Get the metadata catalog for the service application.
  9. IMetadataCatalog catalog =
  10. bdcService.GetDatabaseBackedMetadataCatalog(SPServiceContext.Current);
  11. //Get the LOB system instance for the PartsManagement BDC model.
  12. ILobSystemInstance lobSystemInstance =
  13. catalog.GetLobSystem(systemName).GetLobSystemInstances()[systemName];
  14. //Retrieve a machine by ID value.
  15. Identity identity = new Identity(machineId);
  16. IEntity entity = catalog.GetEntity(nameSpace, entityName);
  17. IEntityInstance instance = entity.FindSpecific(identity, lobSystemInstance);
  18. return instance;
  19. }

IEntity 对象的某个方法接受 ILobSystemInstance 类型的对象作为参数时,例如此处所示的
FindSpecific 方法,它通常要查询外部系统以获取信息。IEntity 对象定义了构造型操作,您可以使用这些操作与外部系统中特定类型的数据实体交互;而
ILobSystemInstance 对象定义了实际连接到具体的外部系统实例时所需的详细信息。通常,您对 IEntityInstance 对象或
IEntityInstance 对象集合执行数据操作。每个 IEntityInstance 对象都包含一组字段和值,与外部系统中的相关数据项相对应。这些字段可表示简单类型或复杂类型。

这种数据访问方法在所有 BDC 对象模型操作中都基本一致。首先从 BDC 模型检索实体和关联的定义,然后将这些实体和关联与 LOB 系统实例结合使用,以便从外部系统检索信息。尽管初看起来这似乎不是很自然,但是您可以通过这种方法将应用程序与后端服务或数据库的实现细节分离。

在 SharePoint 2010 中访问数据Note:

注意: 请务必理解实体与实体实例之间的区别。IEntity 类表示 BDC 模型中的实体,该实体与外部内容类型相对应。该实体的实例由
IEntityInstance 类表示,您可以将其视为列表项或数据行。查询多个实体实例的方法通常返回一个 IEntityInstanceEnumerator 对象,该对象表示可枚举的
IEntityInstance 对象集合。

当您使用 BDC 对象模型与数据存储交互时,实际上是调用您在 BDC 模型中定义的构造型操作。有三种构造型操作对查询数据尤其有用: Finder 方法、SpecificFinder 方法和
AssociationNavigator 方法。

  • Finder 方法。这些方法通常通过向 BDC 上的特定实体的实体实例集合应用筛选条件,返回多个实体实例。
  • SpecificFinder 方法。这些方法返回单一实体实例,但您需要提供其标识符。
  • AssociationNavigator 方法。这些方法返回通过 BDC 模型中的某个关联与指定的实体实例相关联的实体实例。

当您使用 BDC 对象模型来调用这些方法时,该方法大体上与上述示例所示的方法相同。在每种情况下,您都需要:

  • 从元数据目录中检索表示所需实体(外部内容类型)的 IEntity 对象。
  • 通过传入 LobSystemInstance 对象和其他相关参数(例如筛选器或标识符值),调用 IEntity 实例上的方法以便调用操作。

在随后的各个部分中,我们将展示一些有关如何使用每一种构造型操作的实例。

在 SharePoint 2010 中访问数据Note:

注意: 随后的示例来自《开发 SharePoint 2010 应用程序》联机指南中的“外部数据模型参考实现”。有关该参考实现和联机指南的详细信息,请参阅本章最后的“更多信息”部分。

使用 Finder 方法来查询数据

Finder 方法根据任何筛选条件,返回指定实体的所有实体实例。Finder 方法通常在 BDC 模型中包括一个或多个筛选器描述符。当您调用 Finder 方法时,可以检索并使用这些筛选器来约束结果集。Finder 方法经常被称为读取列表 操作。例如,在
PartsManagement BDC 模型中的 Machines 实体定义中,读取列表方法(符合 Finder 构造型的方法)定义了以下筛选器描述符:

<FilterDescriptors>
<FilterDescriptor Type="Wildcard"
FilterField="ModelNumber"
Name="ModelNumberWildcardFilter">
<Properties>
<Property Name="CaseSensitive" Type="System.Boolean">false</Property>
<Property Name="DontCareValue" Type="System.String"></Property>
<Property Name="IsDefault" Type="System.Boolean">false</Property>
<Property Name="UsedForDisambiguation"
Type="System.Boolean">false</Property>
<Property Name="UseValueAsDontCare" Type="System.Boolean">true</Property>
</Properties>
</FilterDescriptor>
<FilterDescriptor Type="Limit"
FilterField="ID"
Name="Filter">
<Properties>
<Property Name="CaseSensitive" Type="System.Boolean">false</Property>
<Property Name="IsDefault" Type="System.Boolean">false</Property>
<Property Name="UsedForDisambiguation"
Type="System.Boolean">false</Property>
</Properties>
</FilterDescriptor>
</FilterDescriptors>

在本例中,为 Machines 实体上的 ModelNumber 字段定义了一个通配符筛选器。利用它,就可以搜索型号中包含指定文本的机器。若要检索与某个筛选器匹配的一组机器,可按以下代码示例所示,调用表示机器的
IEntity 实例上的 FindFiltered 方法。


  1. public DataTable GetMachinesByModelNumber(string modelNumber)
  2. {
  3. //Get the Machines entity (external content type) from the metadata catalog
  4. IEntity entity = catalog.GetEntity(Constants.BdcEntityNameSpace, "Machines");
  5. //Get the filters defined on the default Finder method for the entity
  6. IFilterCollection filters = entity.GetDefaultFinderFilters();
  7. //Set the Wildcard filter value
  8. if (!string.IsNullOrEmpty(modelNumber))
  9. {
  10. WildcardFilter filter = (WildcardFilter)filters[0];
  11. filter.Value = modelNumber;
  12. }
  13. //Return the filtered list of items from the external data source
  14. IEntityInstanceEnumerator enumerator = entity.FindFiltered(filters,
  15. lobSystemInstance);
  16. //Convert the filtered list of items to a DataTable and return it
  17. return entity.Catalog.Helper.CreateDataTable(enumerator);
  18. }

在 SharePoint 2010 中访问数据Note:

注意: 此示例使用 IMetadataCatalog.Helper.CreateDataTable 方法,将结果集作为
DataTable 实例返回。CreateDataTable 方法是 SharePoint 2010 中的一种新的 BCS 方法,可轻松将来自 BDC 的结果集转换为
DataTable 对象。

Finder 方法通常包括以下各种筛选器定义:

  • LIMIT 筛选器确保读取列表操作所返回的结果数量不会超过 BCS 所允许的最大数量。默认情况下,此最大值被设置为 2,000 条记录。强烈建议使用 LIMIT 筛选器来防止您在处理大量数据时出现性能降级问题。
  • WILDCARD 筛选器可以根据部分搜索匹配来筛选读取列表操作所返回的结果。用户可以通过提供一些文本字符(可根据需要包含通配符),对结果集进行限制。通过在 Finder 方法中包括 WILDCARD 筛选器,业务数据 Web 部件就可以使用其内置的搜索功能。
  • COMPARISON 筛选器可以将读取列表操作所返回的结果限制为其字段值与某些搜索文本精确匹配的结果。COMPARISON 筛选器可用来计算条件,例如等于、不等于、小于、大于等等。在 Finder 方法中包括 COMPARISON 筛选器,也可以在业务数据 Web 部件中启用精确匹配以及由条件驱动的筛选。

筛选器也可以向外部系统提供上下文信息,例如要在记录日志时使用的跟踪标识符。

使用 SpecificFinder 方法来查询数据

SpecificFinder 方法返回单一实体实例,该实例的标识符字段值与您提供给此方法的参数相匹配。SpecificFinder 方法经常被称为读取项 操作。若要检索具有特定标识符的机器,可按以下代码示例所示,调用表示机器的
IEntity 实例上的 FindSpecific 方法。请注意,必须将标识符值打包到一个
Identity
对象中,才能将其传递给 FindSpecific 方法。


  1. private IEntityInstance GetBdcEntityInstance(int identifier, string entityName)
  2. {
  3. //Create an identifier object to store the identifier value
  4. Identity id = new Identity(identifier);
  5. //Return the entity on which to execute the SpecificFinder method
  6. IEntity entity = catalog.GetEntity(Constants.BdcEntityNameSpace, "Machines");
  7. //Invoke the SpecificFinder method to return the entity instance
  8. IEntityInstance instance = entity.FindSpecific(id, lobSystemInstance);
  9. return instance;
  10. }

使用 AssociationNavigator 方法来查询数据

从概念上来说,BDC 模型中各个实体之间的关联与关系数据库中的外键约束或一般的 SharePoint 列表中的查找列类似。但是,它们的工作原理并不相同。关联被定义为实体中的方法,可用于从该实体的实例导航到相关实体的实例。不能在 BDC 模型中跨关联创建联接。而必须检索一个实体实例,然后使用关联方法导航到相关的实体实例。

AssociationNavigator 方法返回一组实体实例,这些实例通过指定的关联与指定的实体实例相关联。若要检索与指定的机器相关联的零件集合,可以调用表示机器的
IEntity 对象上的 FindAssociated 方法。FindAssociated 方法需要四个参数:

  • 一个 EntityInstanceCollection 对象。此对象包含要查找相关实体实例的实体实例。在本例中,我们的
    EntityInstanceCollection
    包含一个实体实例,该实例表示具有指定机器 ID 的机器。
  • 一个 IAssociation 对象。此对象包含关联导航 方法实例。我们从表示机器的实体定义中检索此对象。
  • 一个 LobSystemInstance 对象。此对象表示 BDC 模型中某个外部系统的具体实例。
  • 一个 OperationMode 枚举值。如果值为 Online,表示应该从外部系统检索数据;如果值为
    Offline,则表示应该从本地缓冲中检索数据。

在下面的代码示例中阐释了这一点,该示例根据指定的机器 ID 检索一组零件。


  1. public DataTable GetPartsByMachineId(int machineId)
  2. {
  3. //Return the Parts entity - this entity is the destination entity as modeled
  4. //in the Association method
  5. IEntity entity = catalog.GetEntity(Constants.BdcEntityNameSpace, "Parts");
  6. //Return the association defined on the Parts entity which associates the
  7. //Parts entity with the Machines entity
  8. IAssociation association =
  9. (IAssociation)entity.GetMethodInstance("GetPartsByMachineID",
  10. MethodInstanceType.AssociationNavigator);
  11. //Return the Machine entity instance for a given Machine ID - this entity is
  12. //the source entity as modeled in the Association method
  13. IEntityInstance machineInstance = GetBdcEntityInstance(machineId, "Machines");
  14. //Create an EntityInstanceCollection to hold the Machine entity instance
  15. EntityInstanceCollection collection = new EntityInstanceCollection();
  16. //Add the Machine entity instance to the EntityInstanceCollection
  17. collection.Add(machineInstance);
  18. //Execute the association method on the destination entity (Parts) to
  19. //return all the parts for a given machine
  20. IEntityInstanceEnumerator associatedInstances =
  21. entity.FindAssociated(collection, association, lobSystemInstance,
  22. OperationMode.Online);
  23. //Convert the associated list of items to a DataTable and return it
  24. return entity.Catalog.Helper.CreateDataTable(associatedInstances);
  25. }

其他需要使用 BDC 对象模型的方案

除了本主题中介绍的操作以外,还有其他一些外部数据访问方案,在这些方案中,您必须使用 BDC 对象模型来编写自定义代码,而不能使用更简单的机制,例如外部列表或业务数据 Web 部件。这些方法包括:

  • 希望执行批量回写操作,将多行数据写入外部系统内的同一个实体中。例如,您可能需要将多行项添加到某个用户创建的订单中。
  • 希望同时更新多个实体。例如,为了提交订单,您可能需要同时更新订单实体、订单行项实体和载体实体。
  • 希望使用 GenericInvoker 构造型操作。GenericInvoker 方法用于调用外部系统上的逻辑,它可以调用具有任意参数和返回值的方法。

结束语

本章介绍了 SharePoint 2010 中的关键数据访问方法,范围涵盖存储在 SharePoint 列表中的数据以及通过 BCS 访问的外部数据。具体讨论并比较了下列数据访问机制:

  • 查询类。可以使用 SPQuery 类和 SPSiteDataQuery 类,通过构造和提交 CAML 查询来检索列表数据。SPQuery 类用于从单个列表中检索数据,并且允许使用 join 谓词。还可以使用
    SPQuery 类来查询外部列表。SPSiteDataQuery 类用于从多个列表中检索数据,但是不支持 join 谓词和外部列表。尽管随着 LINQ to SharePoint 的问世,这些类的使用频率已经降低,但是在许多方案中,它们仍应是您的首选数据访问方法。
  • LINQ to SharePoint。LINQ to SharePoint 提供程序使您能够使用 LINQ 语法来查询列表数据。为了使用 LINQ to SharePoint 提供程序,您通常先要使用 SPMetal 命令行工具,生成强类型的实体类来表示您的 SharePoint 列表。LINQ to SharePoint 提供程序在运行时将 LINQ 表达式动态转换为 CAML 查询。请务必理解 LINQ to SharePoint 提供程序的细微差别,以便确保这些动态生成的
    CAML 查询保持高效。不能使用 LINQ to SharePoint 来查询外部列表。
  • BDC 对象模型。使用 BDC 对象模型,可以通过与 BDC 模型中实体的编程表示形式交互,直接查询和操作外部数据。BDC 对象模型适用于较复杂的操作,以及 SharePoint 列表对象模型不能满足您的应用程序需求的方案。若要通过 BDC 对象模型查询外部数据存储,首先要从 BDC 模型检索实体和关联的定义,然后将这些实体和关联与 LOB 系统实例结合使用,以便从外部系统检索信息。

在 SharePoint 2010 中访问数据Note:

了解实际效果: 《开发 SharePoint 2010 应用程序》联机指南中提供了可下载的参考实现,这些实现演示了本章中介绍的所有数据访问机制。本章中的许多代码示例也来自这些参考实现。您可以在以下位置找到参考实现:   


有关“参考实现: SharePoint 列表数据模型”,请参阅 http://msdn.microsoft.com/en-us/library/ff798373.aspx。   


有关“参考实现: 外部数据模型”,请参阅 http://msdn.microsoft.com/en-us/library/ff798509.aspx。   

可以将每个参考实现部署到 SharePoint 2010 测试环境中,并在闲暇时在 Visual Studio 2010 中浏览源代码。

更多信息

有关本章未涵盖的数据主题,以及除了下面列出的文档以外的更多背景信息,请参阅本书的在线参考书目 (http://msdn.microsoft.com/gg213840.aspx)。

“使用参数 XML 文件重写 SPMetal 默认值”

“使用 LINQ to SharePoint 管理数据”

“如何: 使用 LINQ to SharePoint 写入内容数据库”

上一篇:[转]ANTS Performance Profiler和ANTS Memory Profiler 使用


下一篇:SharePoint 2010 BCS - 简单实例(一)数据源添加