Business Connectivity Services

通过 SharePoint BCS 使用外部 OData 源

Eric White

Microsoft Business Connectivity Services (BCS) 是 Microsoft Office 2010 和 SharePoint 2010 中的一项功能,旨在帮助开发人员和用户将数据放入 SharePoint。在 SharePoint 中呈现外部数据能让用户构建复合应用程序,以便更好地访问关键信息,更方便地与这些信息交互。

BCS 提供了三种基本机制,您可以利用它们将外部数据放入 SharePoint。首先,您可以通过 SQL 查询连接数据库和使用数据库。默认情况下,支持 SQL Server。作出一些改动后,您也可以连接到 MySQL、Oracle 和其他数据库管理系统。

其次,您可以使用 Web 服务,这些服务提供的方法符合方法原型的特定模式。

第三,您可以使用 Microsoft .NET Framework 和 C# 或 Visual Basic 代码连接到数据源。最常用的方法是编写 .NET 程序集连接器。

在本文中,我将为您介绍第三种方法:编写使用开放数据协议 (OData) 源的 .NET 程序集连接器。

为何是 .NET 程序集连接器?

随着 OData 源的蓬勃发展,您可能需要使用这样的源为用户提供有趣的功能。BCS 不具备使用 OData 源的内置功能,但是编写 .NET 程序集连接器来实现此功能则相对简单一些,

而且可以借此方便地演示如何编写 .NET 程序集连接器。另一种方法是编写自定义的连接器以使用 SQL 数据库,但这是不必要的,因为 BCS 本身就可以轻松使用 SQL 数据源。而且,要演示如何编写使用数据库的 .NET 程序集连接器,需要您正确安装和配置数据库。这并不困难,但需要执行一些额外的步骤,会让示例变得复杂。相反,编写利用现有 OData 源的 .NET 程序集连接器则再简单不过了。

这个示例还将告诉您如何使用 OData 实现创建、读取、更新和删除 (CRUD) 操作。您将会看到这有多么简单。

您可能会感到吃惊,创建一个使用 OData 源的 .NET 程序集连接器居然只需编写如此少的代码。数据访问技术已经有长足的进步,OData 让产生数据和使用数据的应用程序之间的互动达到一个新的水平。

请注意,您也可以使用 SharePoint Designer 来建模、开发和发布 BCS 外部内容类型。SharePoint Designer 本身就支持使用相对扁平的数据结构从后端 SQL 数据库和 Web 服务构建业务实体模型。使用 SharePoint Designer 可以简化(并减少)BCS 开发工作。但是,目前它本身不支持 OData 服务。

OData 和 SharePoint

OData 是一项 Web 协议,用于查询和更新使用现有的 Web 技术(例如 HTTP、Atom 发布协议 (AtomPub) 和 JSON)构建的数据。OData 可用于从各种数据源提供和访问信息,包括关系数据库、文件系统、内容管理系统和传统的 Web 站点。要全面了解 OData,请参见 2010 年 6 月发行的《MSDN 杂志》中的“通过开放数据协议构建富 Internet 应用程序”(msdn.microsoft.com/magazine/ff714561)。该文的作者是 Microsoft 数据和建模团队的项目经理 Shane Burgess。

SharePoint Foundation 2010 和 SharePoint Server 2010 提供列表数据作为 OData 源。默认情况下,此功能处于启用状态。如果您的 SharePoint 站点安装在 URL http://intranet.contoso.com,则可以在浏览器中输入 http://intranet.contoso.com/\_vti\_bin/listdata.s 来检索该站点的 SharePoint 列表集。

如果您的公司 SharePoint 2010 站点包括“我的站点”功能,并且假设您的别名是 ericwhite,则可以在浏览器中输入 http://my/sites/ericwhite/_vti_bin/listdata.svc 来查看“我的站点”上提供的列表。在这两种情况下,浏览器中都将显示如图 1 中所示的 Atom 源。

图 1 来自 SharePoint 列表的 OData

使用 .NET Framework 时,利用 OData 源的简单方法是使用 WCF 数据服务。您使用 Visual Studio 添加对 OData 源的服务引用,IDE 自动生成代码,您就可以使用强类型化的类来查询和更新源。我将逐步为您介绍这一过程。

有关这一过程的详细信息,请参见 WCF 数据服务开发人员中心 (msdn.microsoft.com/data/bb931106),那里有初学者指南以及资源的链接。

开始使用

如前所述,SharePoint 2010 提供列表数据作为 OData 源。访问这些源的简便方法是通过 .NET 程序集连接器,我将为您介绍构建该连接器的过程。

在此过程中,我们将创建一个可以在外部列表中显示和维护的外部内容类型。这看起来可能有点有趣,在您完成这个示例后,您将得到一个包含两个列表的 SharePoint 站点,这两个列表中的数据完全相同。一个列表是按照常用方式创建和设置的 SharePoint 列表。另一个列表是一个外部列表,其中显示的数据来自第一个列表的 OData 源。如果您在一个列表中增加或修改记录,另一个列表也会展示这些更改。

这种方法的主要优势在于构建和运行示例会比较容易。您不需要为示例安装基础结构,所需的只是 SharePoint 场,但您要对这个场拥有管理员权限。

如果您还没有该基础结构,运行该示例的最简单方法是下载 2010 Information Worker Demonstration and Evaluation Virtual Machine (bit.ly/gBKog8)。该虚拟机(即 VM)附带 SharePoint 2010、Visual Studio 2010、Office 2010 等许多软件的已安装工作副本。本文中演示的示例无需修改即可直接在此 VM 中运行。

如果您已有 SharePoint 2010 开发环境,可以很容易地修改此示例(随着行文深入我会逐步介绍如何修改)。但是,如果您刚刚接触 SharePoint 开发,希望多尝试几个示例,VM 是最佳选择。

构建此示例的第一步是熟悉使用 OData 处理 SharePoint 列表中的数据。在本示例中,您要连接到一个客户关系管理 (CRM) 系统,该系统使用 OData 提供客户列表。

首先,创建一个 SharePoint 列表,其中包含几条代表客户的记录。

  1. 打开浏览器,浏览到 SharePoint 站点,创建名为 Customers 的自定义列表。
  2. 将 Title 列的名称更改为 CustomerID。
  3. 创建一个新的列,名为 CustomerName,类型是 Single line of text。
  4. 创建一个新的列,名为 Age,类型是 Number。
  5. 添加几条记录。

现在,以管理员身份登录 VM 并启动 Visual Studio 2010。创建新的 Windows 控制台应用程序。在构建这些 OData 示例时,定位到 .NET Framework 4 或 .NET Framework 3.5 进行构建都是可行的。但是,后面构建 .NET 程序集连接器时,您需要定位到 .NET Framework 3.5,因为 SharePoint 2010 目前只支持这个版本。要让 Visual Studio 使用正确的命名空间名称生成类,请将此项目命名为 Contoso。

在后文中,我将讨论 OData 和 BCS .NET 程序集连接器的命名空间名称。要正确生成命名空间名称,您可以做几件具体的事情,在本示例中,如果您将项目命名为 Contoso,命名空间名称将最贴切。

在 Visual Studio 菜单上,单击“项目”,然后单击“添加服务引用”。在“添加服务引用”对话框中,输入 SharePoint 站点的 OData 服务 URL。如果您使用演示用的 VM,该服务 URL 是 http://intranet.contoso.com/\_vti\_bin/listdata.sv

如果您连接的 SharePoint 站点位于其他 URL,您需要相应调整该服务 URL。

单击“转到”。Visual Studio 将尝试访问该位置,并从 SharePoint 站点下载元数据。如果成功,它将在“添加服务引用”对话框的“服务”列表中显示服务名称。因为模拟的是 CRM 系统,所以在“命名空间”字段中输入 Crm。单击“确定”。查看生成的代码非常有意思。单击解决方案资源管理器窗口中的“显示所有文件”按钮,然后展开“Crm”命名空间,再展开 Reference.datasvcmap 并打开 Reference.cs。其内容类似如下所示(为清楚起见,已将注释删除):

namespace Contoso.Crm {
  public partial class TeamSiteDataContext : 
    global::System.Data.Services.Client.DataServiceContext {
    ...

根据您对项目和服务引用命名空间的命名,生成的类的命名空间是 Contoso.Crm。 Customers 列表的类的完全限定名称是 Contoso.Crm.Customers,清楚明了。

另请注意为数据上下文生成的名称。 在本例中是 TeamSiteDataContext。 为这个类生成的名称是基于要连接的 SharePoint 站点的名称。 如果使用演示用的 VM,要连接的默认站点名称则为 Team Site。 如果您的环境与此不同,请注意数据上下文类的名称,以便正确更改示例中的代码。

打开 Program.cs 并使用图 2 中显示的代码进行更新。 如果您未使用演示用的 VM,请相应地调整 OData 服务 URL。 编译并运行该程序,以查看查询的结果。 如您所见,使用 OData 从列表检索数据并不需要很多代码。

图 2 用于 Program.cs 的更新代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using Contoso.Crm;

class Program {
  static void Main(string[] args) {
    TeamSiteDataContext dc =
      new TeamSiteDataContext(new Uri(
      "http://intranet.contoso.com/_vti_bin/listdata.svc"));
    dc.Credentials = CredentialCache.DefaultNetworkCredentials;
    var customers = 
      from c in dc.Customers
      select new {
        CustomerID = c.CustomerID,
        CustomerName = c.CustomerName,
        Age = c.Age,
      };
    foreach (var c in customers) 
      Console.WriteLine(c);
  }
}

使用 OData 的 CRUD 操作

现在我们试着插入一个客户。 您的代码需要创建新的 CustomersItem,并初始化它的值。 然后调用 TeamSiteDataContext.AddToCustomers 方法,将 CustomersItem 对象作为参数传递。 最后,需要调用 TeamSiteDataContext.SaveChanges 实现更改。

在 Program.cs 中,使用以下代码替换客户变量声明和 foreach 循环:

CustomersItem cust = new CustomersItem();
  cust.CustomerID = "JANE08";
  cust.CustomerName = "Jane";
  cust.Age = 22;
  dc.AddToCustomers(cust);
  dc.SaveChanges();

要更新客户,您将查询 Customers 列表,检索要更新的具体客户。然后更新相应的属性。调用 TeamSiteDataContext.UpdateObject 方法。最后,调用 TeamSiteDataContext.SaveChanges 实现更改:

CustomersItem cust = dc.Customers
    .Where(c => c.CustomerID == "BOB01")
    .FirstOrDefault();
  if (cust != null) {
    cust.CustomerName = "Robert";
    dc.UpdateObject(cust);
    dc.SaveChanges();
  }

要删除客户,请查询 Customers 列表,检索要删除的具体客户。 调用 TeamSiteDataContext.DeleteObject 方法。 调用 TeamSiteDataContext.SaveChanges 实现更改:

CustomersItem cust = dc.Customers
  .Where(c => c.CustomerID == "BILL02")
  .FirstOrDefault();
if (cust != null) {
  dc.DeleteObject(cust);
  dc.SaveChanges();
}

如您所见,使用 OData 修改 SharePoint 列表很简单。

构建 .NET 程序集连接器

在构建 .NET 程序集连接器的过程中,您将定义一个类,该类代表要呈现为外表内容类型的实体。在本示例中,您定义一个 Customer 类,代表 Customers 列表中的一项。有些方法(例如创建客户的方法)将这个类的实例作为参数。另一些方法(例如返回列表中所有客户的方法)返回这个类的实例集合。

您还将配置一个 BCS 模型,该模型用 XML 语言描述这个类。采用基础结构的 BCS 将使用该 BCS 模型的 XML 定义中的信息,以便可以从 SharePoint 2010 中使用外部内容类型。

如果说有什么重要内容是您应该从本文中学习的,那就是:您必须让模型与实际定义的类完全匹配。

您可以借助工具让 BCS 模型定义与实际的类相匹配。但是,关键的一点是无论用哪种方法,您都需要仔细验证模型是否与实际的类匹配。

在更为典型的 BCS 实现中,您将定义很多这样的类,并在 BCS 模型定义中为它们建模。在实现复杂的 BCS 解决方案时,大部分的工作是使模型与类匹配。

现在,让我们构建连接器。在此示例中,您将构建只读的 .NET 程序集连接器,一旦您完成基础工作,添加剩余的 CRUD 功能就变得很简单。

本文的下载代码中包括实现 CRUD 功能的代码和 BCS 模型,无需修改即可在演示用的 VM 中运行。

以管理员身份登录您的 SharePoint 开发计算机。您必须具有场管理员权限才能构建和部署 .NET 程序集连接器。

启动 Visual Studio 2010。创建新项目。创建新的 SharePoint 2010 Business Data Connectivity (BDC) 模型应用程序。和前面一样,您必须定位到 .NET Framework 3.5。将项目命名为 Contoso,然后单击“确定”。我们再一次使用项目名称 Contoso,该名称将用在命名空间中。

在“SharePoint 自定义向导”中,您可以输入 SharePoint 站点的本地站点 URL 以用于调试。如果使用演示用的 VM,则在此向导中,URL 默认正确设置为 http://intranet.contoso.com。如果您的 SharePoint 站点位于其他地址,请更改此 URL。此向导还会告诉您,此项目将部署为场解决方案。单击“完成”。向导运行时请稍作等待。

在解决方案资源管理器中,将 BDC 模型节点从 BdcModel1 更名为 ContosoBdcModel。

接下来,打开 BDC 资源管理器窗格(默认位于解决方案资源管理器的右侧)。将三个 BdcModel1 节点重命名为 ContosoBdcModel。在 BDC 资源管理器中,您不能直接更改树控件中的各个节点,而是需要选择每个节点,然后在“属性”窗格中修改名称(请参见图 3)。

图 3 更改模型名称

下一步是重命名实体,为实体指定标识符。在 BDC Designer 中选择 Entity1。选择实体之后,可以在“属性”窗格中更改其名称。将名称改为 Customers,将命名空间改为 Contoso.Crm。

在 BDC Designer 中,单击 Identifier1,将其名称改为 CustomerID。您还需要在 BDC 资源管理器中设计实体。这部分需要精确完成。如果 BDC 模型与要使用的实际类之间出现不匹配,结果将是无法预料的,错误消息并不总能提供有用信息。在某些情况下,唯一能帮助您判断错误的依据是外部内容类型的列表 Web 部件未加载。

展开 BDC 资源管理器中的节点,直到在 ReadItem 方法的 id 参数下找到 Identifier1 节点。将其名称更改为 CustomerID。展开树,直到在 ReadItem 方法的 returnParameter 下找到 Entity1。将实体的名称更改为 Customers,将类型名称更改为 Contoso.Crm.Customers, ContosoBdcModel。

您要彻底重定义 Customer 实体,因此请从 Customers 实体删除 Identifier1 和 Message 节点。

右键单击 Customers,然后单击“添加类型描述符”。将新类型描述符的名称更改为 CustomerID。默认情况下,新类型描述符的类型设置为 System.String,您可以在此示例中使用。在“属性”窗格中,向下滚动到“标识符”字段。使用下拉列表将其值更改为 CustomerID。

再次右键单击 Customers,然后单击“添加类型描述符”。重命名为 CustomerName。它的类型是 System.String,这是正确的。

添加另一个类型描述符,重命名为 Age,并将其类型改为 System.Int32。进行这些更改之后,BDC 资源管理器窗格将如图 4 中所示。展开 ReadList 节点,再展开 returnParameter 节点并将 Entity1List 重命名为 CustomersList。类型名称设置为如下所示:

System.Collections.Generic.IEnumerable`1[[Contoso.BdcModel1.Entity1, ContosoBdcModel]]

图 4 完成的 Customers 实体

这种语法,即反勾号后跟 1 (`1) 是代表只有一个类型参数的泛型类的语法。后面双方括号中的类型由完全限定的类型以及包含该类型的 BDC 模型的模型名称组成。将类型名称更改为:

System.Collections.Generic.IEnumerable`1[[Contoso.Crm.Customer, ContosoBdcModel]]

这对应着类型 IEnumerable<Contoso.Crm.Customer>,其中的 Customer 类型可在 ContosoBdcModel 中找到。

删除属于 CustomersList 节点的子节点 Entity1。 复制刚刚被您配置为 ReadItem 方法的 returnParameter 的子类型描述符的 Customers 实体。

选择 ReadList 方法的 returnParameter 下的 CustomersList 节点,然后粘贴 Customers 实体。

返回解决方案资源管理器窗口,编辑 Feature1.Template.xml 文件。 添加 SiteUrl 属性,其值是 SharePoint 站点的 URL:

<?xml version="1.0" encoding="utf-8" ?>
<Feature xmlns="http://schemas.microsoft.com/sharepoint/">
  <Properties>
    <Property Key="GloballyAvailable" Value="true" />
    <Property Key="SiteUrl" Value="http://intranet.contoso.com" />
  </Properties>
</Feature>

将 Entity1.cs 重命名为 Customers.cs。 使用以下代码替换 Customers.cs 的内容,这段代码定义了程序集连接器的 Customer 实体:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Contoso.Crm {
  public partial class Customer {
    public string CustomerID { get; set; }
    public string CustomerName { get; set; }
    public int Age { get; set; }
  }
}

使用图 5 中所示的代码替换 CustomersService.cs 中的代码,图中的代码定义了检索一个项和检索一组项的方法。

图 5 CustomersService.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using Contoso.Crm;

namespace Contoso.Crm {
  public class CustomersService {
    public static Customer ReadItem(string id) {
      TeamSiteDataContext dc = new TeamSiteDataContext(
        new Uri("http://intranet.contoso.com/_vti_bin/listdata.svc"));
      dc.Credentials = CredentialCache.DefaultNetworkCredentials;
      var customers = 
        from c in dc.Customers
        where c.CustomerID == id
        select new Customer() {
          CustomerID = c.CustomerID,
          CustomerName = c.CustomerName,
          Age = (int)c.Age,
        };
      return customers.FirstOrDefault();
    }

    public static IEnumerable<Customer> ReadList() {
      TeamSiteDataContext dc = new TeamSiteDataContext(
        new Uri("http://intranet.contoso.com/_vti_bin/listdata.svc"));
      dc.Credentials = CredentialCache.DefaultNetworkCredentials;
      var customers = 
        from c in dc.Customers
        select new Customer() {
          CustomerID = c.CustomerID,
          CustomerName = c.CustomerName,
          Age = (int)c.Age,
        };
      return customers;
    }
  }
}

按照我在本文开头介绍的过程,添加对站点的服务引用,指定命名空间为 Crm。 单击“确定”。 单击“构建”|“构建 Contoso”以构建解决方案。 现在单击“构建”|“部署 Contoso”以部署解决方案。

为所有通过身份验证的用户赋予权限,以访问这个外部内容类型。 打开“管理中心”,单击“管理服务应用程序”。 单击“Business Data Connectivity Service”。 单击您刚添加的外部内容类型旁边的向下箭头,然后单击“设置权限”。 单击文本框下面的“浏览”按钮。 单击“所有用户”,然后单击“所有通过身份验证的用户”。 单击“添加”按钮,然后单击“确定”。

回到“设置对象权限”窗口,单击“添加”按钮,然后为“编辑”、“执行”、“在客户端中可选”和“设置权限”设置权限。 单击“确定”。

浏览到 SharePoint 站点。 创建新的外部列表。 将列表名称指定为 CustomersExt。 单击外部内容类型浏览器。 在“外部内容类型选取器”中,单击您刚创建的外部内容类型。 如果您使用演示用的 VM,刚创建的内容类型将是列表中唯一的外部内容类型。 单击“确定”。

单击“创建”按钮,等一小会儿,如果一切正常,您将看到一个新的外部列表,其中包含与 Customers 列表相同的数据。

如果您向常规列表中添加记录,您将看到记录也会显示在外部列表中。 您可以稍稍改动一下数据。 在常规的 SharePoint 列表中添加、删除或更改某些记录,然后看看外部列表中显示的数据。

最后的总结

如您所见,使用 WCF 数据服务查询和修改 OData 源非常简单。 通过 .NET 程序集连接器创建外部内容类型也是非常简单的过程。 您可以使用 .NET 程序集连接器连接几乎所有数据源。

这个简单示例用到了随时可访问的数据源,但其模式可以轻松扩展到几乎所有提供 OData 源的数据源。 要深入了解 BCS 自定义,请参见 MSDN 库中的“Business Connectivity Services:操作方法和演练”(msdn.microsoft.com/library/ee557949)。 MSDN 数据开发人员中心 (msdn.microsoft.com/data/bb931106) 和开放数据协议网站 (odata.org) 中也有很多关于 OData 的有用信息。

Eric White 是一位独立软件开发人员,他在各种平台上开发企业应用程序的职业生涯长达 25 年。目前,他主要关注 Microsoft Office 技术,包括 Open XML、SharePoint 和 Office 客户端开发。您可以通过他在 twitter.com/EricWhiteDev 上的 Twitter 或在 ericwhite.com/blog 的博客关注他。

衷心感谢以下技术专家对本文的审阅:Ying Xiong