MSDN Magazine > Home > All Issues > 2008 > 八月 >  Data 2.0:在 Web 服务领域公开和使用数据
Data 2.0
在 Web 服务领域公开和使用数据
Elisa Flasko 和 Mike Flasko
代码下载位置: DataServices2008_08.exe (225 KB)
在线浏览代码

本文基于 ASP.NET 3.5 的预发布版本和 Microsoft AJAX 库。文中的所有信息均有可能发生变更。
本文将介绍以下内容:
  • 数据服务的含义
  • 公开和使用数据
  • 使用实体数据模型描述数据
  • 数据安全性
本文使用以下技术:
ADO.NET 数据服务、LINQ 和实体数据模型
回想一下您上次构建的富 Internet 应用程序 (RIA)。您是如何获取您的数据的?又是如何区分该数据与发送至浏览器的演示文稿和用户界面 (UI) 信息的?如果有更简单的方法来实现此操作,会怎样?
分离演示文稿和数据并不是什么新构想,但随着 RIA 技术(如 AJAX 和 Silverlight™)的日益普及,它已经越来越流行了。这些技术基于分离演示文稿和数据的思路构建,其目的在于开发出交互性更强且响应更快的应用程序。
例如,基于 Silverlight 的 RIA 应用程序可预编译代码来驱动演示文稿,并通过 Web 服务器将该代码部署到客户端。然后,到达 Web 浏览器后,该代码将回调到 Web 服务器以检索要在用户界面中显示的数据。此类技术通常不需要选择服务器端呈现过程,因为这会混合数据和演示文稿代码。
除了分离演示文稿和数据以使 Web 体验更丰富、更具交互性之外,Web 还有公开和使用独立于任何用户界面的独立数据的趋势。数据驱动的应用程序(如“资源聚合”)的广泛应用表明,有意义且便于使用的数据的推广开创了新的应用程序方案。
基于对这些趋势的观察,ADO.NET 数据服务框架最初旨在帮助那些希望通过自己的 RIA 应用程序中的服务来公开和使用数据的开发人员。探索该领域时,出现了两个主要理念:使用现有方法针对以数据为中心的服务构建通用客户端库和工具这一观念本身,就很有难度;创建和维护这些服务需要投入大量的开发人员。在本文中,我们将重点介绍什么是数据服务以及几个主要功能。如果希望更深入地了解数据服务所隐藏的部分理念,请参阅团队博客上标题为“Why ADO.NET Data Services?”(为什么要构建 ADO.NET 数据服务?)的帖子 (go.microsoft.com/fwlink/?LinkId=120530)。
总的来说,ADO.NET 数据服务框架的目标是为公开和使用以数据为中心的服务创建基于具象状态传输 (REST) 的简单框架。此类服务使用统一的界面公开数据,以供整个企业 Intranet 或 Internet 范围内的所有 Web 客户端使用。该框架由一个服务器库和一组客户端库组成,前者用于将数据作为服务安全公开,后者是为一系列 Microsoft 应用程序和技术(Microsoft® .NET Framework 和 Silverlight 等)能够使用服务而构建的。图 1 展示了该体系结构。
图 1 ADO.NET 数据服务框架体系结构(单击图像可查看大图)

使用实体数据模型描述数据
每个 ADO.NET 数据服务都是使用概念架构定义语言 (CSDL) 借助实体数据模型 (EDM) 术语进行描述的。EDM 使用了两个主要概念:实体和关联(或关系)。实体是“实体类型”的实例(如 Customer 或 Employee),即含有一个关键字的结构化记录。实体关键字基于“实体类型”属性的子集形成。关键字(分别为 CustomerId 和 OrderId)用于唯一标识和允许实体实例更新,还允许实体实例参与到关系中。实体按 EntitySets 分组(Customers 是 Customer 实例的集合)。关联用于在两个或多个实体类型之间形成链接(如 Employee WorksFor Department)。
为什么选择 EDM 作为 ADO.NET 数据服务的数据描述语言?事实证明,EDM 可以很好地映射到资源和链接的主要 Web 概念,因此也就成了理想的候选项(资源的实体和链接的关联)。
EDM 的开发目标是成为一组开发人员和服务器技术的核心数据模型。通过仅使用一个数据模型在大量应用程序之间进行维护,就能够简化应用程序维护工作。EDM 不仅可用于为基于 ADO.NET 数据服务构建的自定义应用程序定义模型,还可以将该模型用作报告和可视化应用程序、Intranet 门户应用程序或工作流应用程序的输入内容。ADO.NET 数据服务是要基于 EDM 概念构建的第二种 Microsoft 技术(第一种技术当然是 ADO.NET 实体框架)。有关 EDM 和 ADO.NET 实体框架的详细信息,请参阅 msdn.microsoft.com/data 和 MSDN® 杂志 2008 年 7 月刊中的“ADO.NET:使用实体框架灵活地为数据建模”,网址为 msdn.microsoft.com/magazine/700331

关系数据
默认情况下,ADO.NET 数据服务会与 ADO.NET 实体框架密切合作,以针对基于在 SQL Server® 或其他第三方数据库(Oracle、DB2 和 MySQL,仅举几个示例)中存储的数据的数据模型,简化连接和公开此类数据模型的过程。
实体框架增加了开发人员处理数据时的抽象级别,即不是针对行和列编码,而是基于关系数据定义一个更高级别的概念模型(如实体数据模型),然后依据此模型对应用程序进行编程。应用程序只需理解应用程序可读懂的形状中的数据,这些形状都是使用包括概念(如继承、复杂类型和显式关系)在内的丰富词汇来表达的。
总的来说,ADO.NET 数据服务的工作原理是:将通过资源执行某个操作的请求(例如,通过 URI 执行的 HTTP 请求操作)转换为该资源代表的数据模型上的等效操作。在这种情况下,由于数据模型由关系数据库支持,所以 URI 将转换为实体框架对象服务方法调用。
只需几个步骤,便可以在 Visual Studio® 2008 SP1 ASP.NET Web 应用程序项目中公开使用实体框架创建的现有数据模型。(同样支持其他项目类型和承载机制,如 Windows® Communication Foundation (WCF) 或 WebServiceHost。)首先在 ASP.NET Web 应用程序中创建或导入一个 ADO.NET 实体数据模型。在该模型可用的情况下,添加新的 ADO.NET 数据服务。“添加新项”向导将生成一项基本服务,并将其显示在解决方案资源管理器中。然后,我们可以通过更改类定义将该服务挂接到此模型,如下所示:
using NorthwindModel;

public class NorthwindService : DataService <NorthwindEntities>
{
     public static void InitializeService(IDataServiceConfiguration config)
     {

     }

}
此外,由于在默认情况下,该服务最初会处于锁定状态,我们必须设法设置服务范围的安全策略,以使用户能够使用该服务。以下代码可在初始化该服务时设置 EntitySet 级别的安全性:
public static void InitializeService(IDataServiceConfiguration config)
{
    config.SetEntitySetAccessRule("*", EntitySetRights.All);
}
您可以看到,这是设置 ADO.NET 数据服务安全性的一个非常简单的示例。稍后将讨论其他安全方法和注意事项。
此时,我们便拥有了一个可创建和运行的工作 ADO.NET 数据服务。
在没有客户端应用程序的情况下测试服务的简单方法是,您只需将 Web 浏览器指向该服务的入口点,如 <host>/<vdir>/service.svc。该服务的入口点将返回 XML 格式的响应,其中包含数据服务公开的 EntitySets 的列表。若要在 Internet Explorer® 中查看 Atom(ADO.NET 数据服务返回的默认格式),必须首先确保已关闭 Internet Explorer 中的“源阅读视图”。可以通过“工具”|“Internet 选项”的“内容”选项卡来完成此操作。

关于非关系数据
为了获得所有其他数据源或使用其他数据库访问技术(如 LINQ to SQL),现提供了一个机制,该机制可使用插件模型将任何数据源作为 ADO.NET 数据服务公开。
在这种情况下,URI 将转换为 LINQ 查询。数据服务可将实现 IQueryable 接口的对象映射到 EntitySets,从而启用此方法。这意味着只要为数据源写入了 IQueryable 提供程序,ADO.NET 数据服务就能够公开该数据源。为实现此功能,ADO.NET 数据服务定义了 CLR 对象与基于 EDM 的数据模型的产物之间的映射。
稍后,我们将探讨如何根据简单的 CLR 对象图创建 ADO.NET 数据服务。我们即将介绍的步骤会生成一个数据服务,此服务是基于内存中的对象集合创建的。不过,在典型的生产部署中,所显示的代表 EntitySets 的 IQueryable 属性不会公开内存中的数据,但会将 IQueryable 表达式树转换为特定于数据源的查询。例如,LINQ to Entities 可将 IQueryable 表达式树转换为 SQL 语句。
在针对非关系存储创建数据服务时,会同时在 ASP.NET Web 应用程序项目中创建该数据服务。但是,此时请注意,我们将跳过用于创建 ADO.NET 实体数据模型的步骤,而从添加新的 ADO.NET 数据服务开始。“添加新项”向导将生成一项基本服务,此服务将在解决方案资源管理器中列出。若要继续介绍此简单示例,现在必须创建计划通过我们的服务公开的内存中数据。为此,我们创建三个类,其中两个类分别是 User 和 Contact,每个 User 都包含一组 Contact。第三个类是 MyDataService 类,该类将两个公共属性(Contacts 和 Users)作为 IQueryable<Contact> 和 IQueryable<User> 提供,如图 2 所示:
public class User
{
     public int ID { get; set; }
     public string Name { get; set; }
     public IList<Contact> Contacts { get; set; }
}

public class Contact
{
     public int ID { get; set; }
     public string Name { get; set; }
     public string Email { get; set; }
}

public class MyDataService
{
     static User[] _users;
     static Contact[] _contacts;

     static MyDataService()
     {
        _users = new User[]{ new User{ID=1, Name="Mike"},
                 new User{ID=2, Name="Elisa"} };

         _contacts = new Contact[]{ new Contact{ID=1, Name="Joe", 
                              Email="Joe@contoso.com"},
                  new Contact{ID=2, Name="Bob", Email="Bob@contoso.com"},
                  new Contact{ID=3, Name="Sam", Email="Sam@contoso.com"},
                  new Contact{ID=4, Name="Carl", Email="Carl@contoso.com"}, 
                  new Contact{ID=5, Name="Abby", Email="Abby@contoso.com"},
                  new Contact{ID=6, Name="Annie", Email="Annie@contoso.com"},
                  };

         _users[0].Contacts = new List<Contact>();
         _users[0].Contacts.Add(_contacts[0]);
         _users[0].Contacts.Add(_contacts[1]);
         _users[0].Contacts.Add(_contacts[4]);

         _users[1].Contacts = new List<Contact>();
         _users[1].Contacts.Add(_contacts[2]);
         _users[1].Contacts.Add(_contacts[3]);
         _users[1].Contacts.Add(_contacts[5]);
    }

    public IQueryable<User> Users
    {
         get { return _users.AsQueryable<User>();}
    }

    public IQueryable<Contact> Contacts
    {
         get { return _contacts.AsQueryable<Contact>(); }
    }
}
既然已确定了数据源,我们现在返回熟悉的领域,即更改类定义使其指向我们的数据源,如下所示:
public class contacts : 
  DataService<ADONETDataServiceNonRelSample.MyDataService>
{
    public static void InitializeService(IDataServiceConfiguration config)
    {
          config.SetEntitySetAccessRule("*", EntitySetRights.All);
    }
}
同样,我们必须做一些初始工作来设置安全策略,因为默认情况下初始服务处于锁定状态。我们在前面已经了解到,此代码显示 EntitySet 级别的相同安全策略设置。此时,我们便再次拥有了一个可以创建和运行的工作 ADO.NET 数据服务。只要使用 Internet Explorer 浏览至服务端点即可完成对该服务的测试。

统一 URI 格式
统一的统一资源标识符 (URI) 是组成 ADO.NET 数据服务的主要概念之一,它定义了一个相对简单、但富有表现力的 URI 格式,该格式允许应用程序查询实体组或单个实体,以及遍历这些实例之间存在的关系。此 URI 的结构使代理和控件能够轻松了解如何导航由服务提供的数据。
当开始测试初始服务时,我们可以看到指向数据服务本身的基本 URI 格式。根据此基本 URI 格式,我们能够使用以下格式查询服务,如下所示:
http://<host>/<vdir>/<service.svc>/<EntitySet>[(<Key>)
[/<NavigationProperty>[(<Key>)/...]]] 
例如,继续讨论之前的 Northwind 示例,如果我们将 /Customers 添加到数据服务 URI 的末尾,即 <host>/<vdir>/NorthwindService.svc/Customers,我们将返回 Customers 实体集中的所有客户,在本例中,将返回 Northwind 数据库中的所有 Customers。另一方面,如果希望返回 Northwind 服务中的单个客户实体,我们可以使用该实体的键值,因为 Customer 是一个单键实体。通过请求 URI <host>/<vdir>/NorthwindService.svc/Customers('ALFKI'),可以返回带有“ALFKI”键的单个 Customer。以类似的方式将导航属性附加到单个实体查询 URI。通过追加 /Customers('ALFKI')/Orders,我们可以导航模型中 Customer 和 Order 之间的关系,从而返回与使用“ALFKI”键标识的 Customer 相关联的所有 Order。
上述 URI 格式允许在我们的存储中进行基本查询以及遍历实体,但是不负责进一步控制查询的输入或对其加以约束。为了实现这些目标,我们将使用一组受 ADO.NET 数据服务支持的可选查询字符串参数,包括 $filter、$expand、$orderby、$skip 和 $top。查询字符串参数追加在 URI 中 ?字符后方。
例如,要查询伦敦的所有客户,我们可以对服务 URI 追加 /Customers?$filter=City eq 'London'。要查询特定客户(具有键“ALFKI”)并检索相关的销售订单,我们可以对服务 URI 追加 /Customers('ALFKI')?$expand=Orders。若要按 City 对结果进行升序 (asc) 排序,我们可以对服务 URI 追加 /Customers?$orderby=City asc(若要进行降序排序,则追加 /Customers?$orderby=City desc)。
若要获得受 ADO.NET 数据服务支持的 URI 格式的完整列表,请查看数据服务文档,可从 go.microsoft.com/fwlink/?LinkId=120539 中访问该文档。

数据服务安全性
当讨论基于 Web 的技术,特别是提供对关键数据的访问和操作的技术时,安全性始终是首要关注的问题。因此,在数据服务的整个开发周期中,安全性是主要的考虑因素。
为了提供身份验证,ADO.NET 数据服务将利用大量的现有 ASP.NET 和 WCF 身份验证基础结构,以便用户在应用程序之间和整个服务中获得集成的身份验证体验。如果您拥有一个使用身份验证(可能是某个身份验证提供程序,也可能是适当地设置了 HTTP 上下文主体的自定义提供程序)的 ASP.NET 站点,ADO.NET 数据服务就可以利用您选择的机制为任一给定的请求建立当前身份标识(主体)。同样,如果您在 ASP.NET 之外(WCF 或通过自定义主机)托管数据服务,主机也可以选择使用任何身份验证机制,但前提是它能够为数据服务作者提供访问请求主体所需的 API。
默认情况下,任何新 ADO.NET 数据服务都处于完全锁定状态,此时,对于没有读写访问权限即无法访问的任何实体、服务操作和元数据,都将无法访问。尽管如此,仍为您的数据服务提供了多种机制来控制授权策略和按请求执行验证。
数据服务开发人员首先要执行的步骤之一是针对由数据服务提供的资源提供访问权限。在我们的示例中,我们已展示了一个简单案例,介绍如何设置整个服务的读/写访问策略。此操作是使用 InitializeService 方法针对每项服务执行的。策略集是 EntitySet 策略,用于允许或限制对模型中实体集的访问,以及设置从服务的元数据终结点可以获得哪些实体集和相关的关联。尽管这些示例中显示的这两个策略都针对整个模型开放了读取和写入访问权限,但我们可能要特别得多,即为不同的 EntitySets 设置不同的策略并在必要时包含复合策略,如图 3 所示。
public class MyService : DataService<NorthwindEntities>
{
   public static void InitializeService(
      IDataServiceConfiguration config)
   {
       // '/Customers' entities are enabled for all read and write 
       // operations
       config.SetEntitySetAccessRule("Customers", 
          EntitySetRights.All);

       //  URI '/Orders' is disabled, but '/Orders(1)' is enabled for read 
       //  only 
       config.SetEntitySetAccessRule("Orders",
           EntitySetRights.ReadSingle);

       //  Can insert and update, but not delete Products
       config.SetEntitySetAccessRule("Products",
           EntitySetRights.WriteInsert |
           EntitySetRights.WriteUpdate);
   }
}
在许多情况下,还要求数据服务在实体进入数据服务(进行插入、更新或删除)时运行验证逻辑,并可限制每个请求对实体的访问。对于这些情况,您可以利用侦听器,使数据服务开发人员能够将自定义验证或访问策略逻辑插入数据服务的请求/响应处理管道。例如,如果我们希望使客户只能检索自己的订单,而不能检索其他客户发出的订单,我们将需要实现一个查询侦听器,如图 4 所示。查询侦听器将返回一个要附加到查询的谓词,然后,该查询将被推入对应的数据存储,无需对该数据存储执行其他操作即可检索访问控制信息。
public class nw : DataService<NorthwindModel.NorthwindEntities>
{
     public static void InitializeService(IDataServiceConfiguration config)
     {
         config.SetEntitySetAccessRule("Orders", EntitySetRights.All);
     }

     [QueryInterceptor("Orders")]
     public Expression<Func<Orders,bool>> OnQueryOrders()
     {
         return o => o.Customer.ContactName == 
             HttpContext.Current.User.Identity.Name          
     }
}
同样,还可以为插入、更新和删除操作添加更改侦听器。更改侦听器没有返回值但有两个参数:一个包含在 EntitySet 中的类型对象,一个用于定义资源请求的操作(更新、插入或删除)的 UpdateOperations 枚举。该侦听器可以更改传递给它的对象或设置对完全不同的实例的引用。如果该方法引发异常,将中止操作,数据库中不会发生任何更改,并为客户端代理返回一个错误。

客户端:访问服务
通常,我们将 HTTP 视为数据服务的 API,因此花费了大量的时间来确保能够轻松地在 HTTP 级别上直接使用它。这意味着,数据服务不仅能够轻松解释服务负载,而且包含 HTTP 堆栈的任何平台都可以轻松使用数据服务。尽管如此,如果您使用 Microsoft 平台访问数据服务,则使用 ADO.NET 数据服务客户端库便可进一步简化客户端开发。这为使用 .NET Framework、Silverlight 和 ASP.NET AJAX 编写的应用程序提供了更为自然的编程模型,可定位目标服务、基于功能集操作结果,以及处理某些方面(如关联遍历)。客户端库可抽象出 HTTP 请求和响应的详细信息,还可确保在各个客户端堆栈之间提供更为统一的体验。
.NET Framework 和 Silverlight 客户端库包含两个主要类型:DataServiceContext 类和 DataServiceQuery 类。DataServiceContext 代表给定数据服务的运行时上下文。数据服务本身是无状态的,但开发人员进行交互时所处的上下文却并非如此,并且为支持标识解析和开放式并发等功能,客户端的状态还会在各交互之间得以保持。DataServiceQuery 对象代表针对使用 URI 语法定义的存储执行的特定查询。若要执行查询并获得 .NET 对象形式的结果,只需枚举该查询对象即可。例如,您可以通过在 C# 中使用标准的“foreach”结构或在 Visual Basic® 中使用标准的“For Each”来实现此操作。
为了将数据服务中定义的实体用作客户端上的 .NET 对象,需要为客户端应用程序定义相应的类。要完成此操作,主要有三种方法可供选择。一种选择是手动定义它们。图 5 显示了 Region 类的简单手写定义和一小段代码,这段代码可针对区域执行查询并在输出中显示区域的 ID 和描述。
namespace TestApplication
{
  public class Region
  {
     public int RegionID { get; set; }
     public string RegionDescription { get; set; }
  }

  class Program
  {
     static void Main(string[] args)
     {
        DataServiceContext ctx = new
           DataServiceContext("http://localhost:25115/NorthwindService.svc");

        DataServiceQuery<Region> regions =
            ctx.CreateQuery<Region>("/Region?$orderby=RegionID");

         foreach (Region r in regions)
         {
            Console.WriteLine(r.RegionID + ", " + r.RegionDescription);
         }
      }
   }
}
更为常用的方法是使用由 Visual Studio 生成的代码。如果当服务只拥有少量的类型时,可以手动编写类,那么随着数据服务架构复杂程度的提高,必须手动创建和维护的类的数量和大小也会大幅度增加。
生成所需类的最常用的方法是使用 Visual Studio 中的“添加服务引用”向导。与针对 WCF 服务使用“添加服务引用”的方法类似,只需右键单击“项目”,然后选择“添加服务引用”(如图 6 所示)。
图 6 添加服务引用(单击图像可查看大图)
在“添加服务应用”对话框中的“地址”中输入服务入口点的 URI。在本例中,我们将输入 <host>/<vdir>/NorthwindService.svc。这将生成基于数据服务定义的类并将它们添加到项目中。
另一种选择是使用“datasvcutil.exe”命令行工具生成基于数据服务定义的类。此工具随 ADO.NET 数据服务的发布版本提供,位于 \Windows\Microsoft.Net\Framework\V3.5 目录下。该工具的使用形式为:参数,后跟数据服务入口点的 URI,最后是将要生成的输出文件的名称和位置,如下所示:
"\Windows\Microsoft.Net\Framework\V3.5\datasvcutil.exe" 
    /out:C:\NorthwindSample\northwind.cs /uri:"http://<host>/<vdir>/
    NorthwindService.svc"
该命令行工具的默认输出是一个 C# 文件,其中包含适用于数据服务中描述的每个实体类型的类(Visual Basic 类型可以使用 /language:VB 开关生成)。生成的每个类中都包含一些成员,用以代表在服务中描述的原始值和关联。这样,开发人员就可以直接使用对象模型来浏览关联实体的图形。

.NET 客户端库
ADO.NET 数据服务 .NET 客户端库提供了一个编程模型,使用 .NET Framework 和数据服务编写应用程序的开发人员都很熟悉此模型。事实上,客户端库使用 HTTP 和 AtomPub 格式,所以,它在公司网络和 Internet 环境中均可正常运行,只需与数据服务之间建立简单的 HTTP 级连接,直接连接或间接连接(例如通过代理)均可。
Windows 窗体、Windows Presentation Foundation (WPF) 和 Web 项目等所有项目类型都可以使用客户端库。为了使用上述客户端库,您需要添加对 System.Data.Services.Client.dll 程序集的引用。

Silverlight 客户端库
Silverlight 客户端不是作为 ADO.NET 数据服务的一部分提供,而是作为 Silverlight 2 SDK 的一部分提供,这样就可以在开发 Silverlight 应用程序时获得集成更加完全的体验。Silverlight 客户端库与 .NET 和 AJAX 客户端库之间的一个区别在于,Silverlight 2 不支持同步开发。因此,使用 Silverlight 客户端库开发时必须利用异步 API,遵循通用的 begin/end 异步模式。这也就意味着,您可以在另外两个客户端库中使用的某些 API 尚未在 Silverlight 客户端库中启用。

AJAX 客户端库
目前,ASP.NET AJAX 库可从 codeplex.com/aspnet/Release/ProjectReleases.aspx?ReleaseId=13357 获得。与前面讲的 .NET 客户端库类似,AJAX 客户端库将提取 HTTP 的详细信息,以便应用程序开发人员可以直接使用 JavaScript 对象,而不需要手动分析和创建 HTTP 请求与响应。CodePlex 站点还提供了许多介绍如何针对 AJAX 应用程序使用数据服务库的网页(请参见 codeplex.com)。

查询数据服务
图 5 中,对服务执行的查询是通过调用 DataServiceQuery.CreateQuery 方法并传入 URI 查询而构建的。此外,该库还允许您使用 LINQ 以公式形式表示数据服务查询。客户端库将 LINQ 语句映射到目标数据服务中的 URI,并将指定的资源作为 .NET 对象进行检索。以下代码显示如何检索伦敦城市内的所有客户,并将检索结果按公司名称进行排序:
var q = from c in ctx.Customers
        where c.City == "London"
        orderby c.CompanyName
        select c;

foreach (var cust in q)
{
    Console.WriteLine(cust.CompanyName);
}
在 LINQ to ADO.NET 数据服务时,可以使用 LINQ 语法表达的查询集要比通过数据服务的基于 REST 的 URI 语法启用的查询集广泛。如果查询无法映射到目标数据服务中的有效 URI,则会引发异常。
了解能够和不能够映射到 URI 的查询类型的一种方法是考虑分层遍历。需要两个或多个数据透视表的任何查询(例如,使用 Any 等子句的联接或子查询)目前无法映射到 URI。但一般情况下,使用一个数据透视表和关联遍历的查询都可以正常工作。
到目前为止,我们查询了简单的实体。对象之间的关联也由 DataServiceContext 跟踪和管理,并且可以使用“统一 URI 格式”部分介绍的 URL 格式自愿或根据需要加载关联的对象。若要根据需要加载关联实体(延迟加载),请对 DataServiceContext 类使用 LoadProperty 方法,如图 7 所示。
// get a single category
DataServiceQuery<Categories> categories =
               context.CreateQuery<Categories>("/Categories(1)");

foreach (Categories c in categories)
{
     Console.WriteLine(c.CategoryName);

     context.LoadProperty(c, "Products");

     foreach (Products p in c.Products)
     {
          Console.WriteLine("\t" + p.ProductName);
     }
}
在某些情况下,我们可能还希望获取实体时可以避免线路上的额外往返行程,更喜欢与原始查询一起加载实体。在本示例中,在 URI 上指定了 Expand 选项。客户端库认为结果中包括顶层实体和关联实体,并将这些实体全部具体化为单个对象图表。图 8 自愿将一次往返行程中的相关产品加载到数据服务。
public IList<Products> GetProducts(string productName, 
    Categories category)
{
     int categoryId = category.CategoryID;
     string uri = serviceUri + "/Categories(" + categoryId + ")/Products?";
     if (!String.IsNullOrEmpty(productName)) 
     {
         uri = uri + "$filter=ProductName eq '" + productName + "'&";
     }
     uri = uri + "$expand=Categories";
     Uri queryUri = new Uri(uri);
     try
     {
         IEnumerable<Products> products = 
            context.Execute<Products>(queryUri);

         return products.ToList();
     }
     catch (Exception)
     {
         return null;
     }                  
}

命令映射
使用 HTTP Get 请求映射通过 ADO.NET 数据服务执行的查询后,如何执行 Create、Update 和 Delete 请求?四个 CRUD 操作(Create、Retrieve、Update 和 Delete)中的每个操作都映射到一个不同的 HTTP 动词:Retrieve 映射到 GET,Create 映射到 POST,Update 映射到 PUT,Delete 映射到 DELETE。与对待查询一样,使用的客户端库(本例中为 .NET 客户端库)将提取详细信息并使用相应的 HTTP 动词,这样开发人员就可以非常轻松地使用提供的 API。
若要为数据服务中的某个实体创建实例,只需创建一个 .NET 对象,并使用所需数据进行填充,然后在 DataServiceContext 对象上调用 AddObject,这就会传入新对象以及这个对象要添加到的 EntitySet 的名称,如下所示:
public void AddProduct(Products product)
{
     context.AddObject("Products", product);
     context.AttachTo("Categories", product.Categories);
     context.SetLink(product, "Categories", product.Categories);
     DataServiceResponse r = context.SaveChanges();
}
还可以添加或更改 .NET 对象之间的关联,并使客户端库将这些更改反映为关联创建/删除操作。例如,上一段代码在 Northwind 数据库中创建了 Product,并将其与一个现有 Category 关联起来。类别和产品之间是一对多关联;因此,一个给定产品对应一个特定类别。
创建实体后,数据服务将返回该实体的全新副本,其中包括在数据库中触发某个操作后更新的值,以及自动生成的关键字等。然后,客户端库将自动使用新值更新 .NET 对象。
若要修改现有的实体实例,首先,客户需要获取该对象并使用 DataServiceContext 对其进行跟踪。开发人员首先会查询该对象或将该对象附加到上下文,对其属性进行必要的更改,然后调用 UpdateObject 方法。UpdateObject 方法指示客户端库对该对象进行更新:
public void UpdateProduct(Products product)
{
    Categories newCategory = product.Categories;
    context.AttachTo("Products", product);
    context.AttachTo("Categories", newCategory);
    context.UpdateObject(product);
    context.SetLink(product, "Categories", newCategory);

    context.SaveChanges();
}
若要删除实体实例,客户端必须再次获取该对象并使用 DataServiceContext 对其进行跟踪。跟踪完毕后,我们可以直接在上下文实例上调用 DeleteObject 方法,标记该对象以进行删除:
public string DeleteProduct(Products product)
{
     context.AttachTo("Products", product);
     context.DeleteObject(product); 
     context.SaveChanges();
}
在上述所有示例中,在上下文中最后一次执行的方法调用是对 SaveChanges 方法的调用。所做的更改是在 DataServiceContext 实例中进行跟踪的,但并未立即发送到服务器。完成给定活动所需的所有更改后,调用 SaveChanges 即可将更改提交到数据服务。

到目前为止,每个示例都生成了一个 HTTP 和针对每个客户端操作的服务器请求。这种方法(每个 HTTP 请求执行一个操作)在某些情况下很适用,但是通常最好对多个操作进行批处理,然后通过一个 HTTP 请求将它们发送到数据服务。这将减少数据服务的往返次数,并为操作集合启用原子性的逻辑范围。为了支持这些要求,客户端支持通过一个 HTTP 请求将 CUD(Create、Update 和 Delete)操作组以及 Query 操作组发送到数据服务。以下代码显示了如何将两个或多个查询作为一个批次发送到数据服务:
var q = (DataServiceRequest)from o in context.SalesOrder
                            select o;

// send two queries in one batch request to the data service
DataServiceResponse r = service.ExecuteBatch(
    new DataServiceRequest<Customer>(new 
      Uri("http://localhost:25115/NorthwindService.svc/Customers")),
      q);
要将一组 CUD 操作作为一个批次发送给数据服务,可直接调用 SaveChanges 方法并将 SaveChangesOptions.Batch 作为唯一的参数传递。此参数值指示客户端将所有挂起的更改操作组合为一个批次,然后将其作为一个原子团发送到数据服务。当使用 SaveChangesOptions.Batch 将更改操作作为一个批次进行发送时,要么会成功完成所有更改,要么不会应用任何更改。

了解更多
尽管本文所含内容已足够让您入门 ADO.NET 数据服务,但您感兴趣的主题可能仍然有很多。要了解详细信息,请访问数据服务团队博客(网址为 blogs.msdn.com/astoriateam),也可以访问 MSDN 数据开发中心(网址为 msdn.microsoft.com/data)下的 ADO.NET 数据服务主题。

Elisa Flasko 是 Microsoft 数据可编程性团队的一名项目经理,主要从事 ADO.NET 技术、XML 技术和 SQL Server 连接性技术的研究。您可以通过 blogs.msdn.com/elisaj 与她联系。

Mike Flasko 是 Microsoft SQL 数据可编程性团队的一名项目经理。您可以通过 Mike 的博客 blogs.msdn.com/mflasko 与他联系。

Page view tracker