ADO.NET Data Services 简介

概述

ADO.NET 数据服务框架包含模式和库的组合,支持为 Web 创建和使用数据服务。

其目标在于使应用程序能够将数据公开为可供企业网络内部和互联网上的 Web 客户端使用的数据服务。ADO.NET 数据服务使用 URL 指向数据片段,利用简单、众所周知的格式(如 ATOM 和 JSON)来显示这些数据。

此外,Web 友好的技术的使用也使之成为 AJAX 风格应用程序、富交互式应用程序和其他需要处理 Web 上的数据的应用程序的理想数据后端。

全新的 ADO.NET Data Services v1.5 CTP1 版本是一个独立版本,代表了以 ADO.NET 数据服务技术的下一个版本为目标的工作进展。此版本以 .NET Framework 3.5 SP1 和 Silverlight 2 平台为目标,为数据服务开发人员提供了全新的客户端和服务器端功能。在这次实验中,您将了解如何使之适应 Visual Studio 2010 Beta 2。下面简要介绍了所包含的增强和新功能:

•              数据绑定:支持 WPF 和 Silverlight 的双向数据绑定。可以生成实现 INotifyPropertyChanged 和 INotifyCollectionChanged 接口的客户端类型。

•              行计数:支持确定一个集中的实体总数,而无须检索这个集中的全部实体。

•              源自定义(也称为“Web 友好的源”):能够自定义如何将实体映射到 Atom 源的各元素。

•              服务器驱动的分页:允许按集合设置为每条请求返回的实体总数的上限,并提供“下一页链接”,指定如何继续检索其余实体。

•              增强的 BLOB 支持:使数据服务能够流式传输大 blob,将二进制内容与其元数据等分别存储。

•              用于自定义提供程序编写器的全新“数据服务提供程序”界面:引入了一种方法,使您能在所支持的提供程序模型不能满足您的需求时,编写“自定义”提供程序。

目标

在本次动手实验中,您将学习如何:

•              创建一个 ADO.NET 数据服务,并通过 .NET Client API 使用它

•              通过 ASP.NET AJAX API 使用 ADO.NET 数据服务

•              为数据服务添加服务操作和侦听器

•              添加带有行计数的客户端分页

               

系统要求

您必须拥有以下内容才能完成本实验:

•             Microsoft Visual Studio 2010 Beta 2

•             ADO.NET Data Services v1.5 CTP1

•              Microsoft SQL 2005 或 Microsoft SQL 2008(速成版或更高版本)

•              Adventure Works 示例数据库

◦              对于 Microsoft SQL 2005:AdventureWorksLT.msi

注意:该 msi 只将示例数据库文件复制到您的文件系统中,您必须手动将数据库附加到 SQL Server。更多信息请参见如何附加数据库 (SQL Server Management Studio)。

Windows Vista 用户提示:应考虑将数据库文件安装在“C:\Program Files\”以外的地址,因为此文件夹具有更高的安全性,您可能由于项目文件是只读的而无法保存项目文件更改。

◦              对于 Microsoft SQL 2008:AdventureWorks 2008 示例数据库

注意:该 msi 会自动将所有示例数据库安装到您的 SQL Server 中。但您只能使用 AdventureWorksLT 数据库。

               

安装

使用 Configuration Wizard 验证本实验的所有先决条件。要确保正确配置所有内容,请按照以下步骤进行。

注意: 要执行安装步骤,您需要使用管理员权限在命令行窗口中运行脚本。

1.            如果之前没有执行,运行 Training Kit 的 Configuration Wizard。为此,请运行 %TrainingKitInstallationFolder%\Labs\ADONetDataServices\Setup 文件夹下的 CheckDependencies.cmd 脚本。安装先决条件中没有安装的软件(如有必要请重新扫描),并完成向导。

注意:为了方便,本实验管理的许多代码都可用于 Visual Studio 代码片段。CheckDependencies.cmd 文件启动 Visual Studio 安装程序文件安装该代码片段。

               

练习

本次动手实验由以下练习组成:

•              创建和使用 ADO.NET 数据服务

•              通过 ASP.NET AJAX API 使用 ADO.NET 数据服务

•              使用服务操作和侦听器扩展数据服务

•              添加带有行计数的客户端分页

               

完成本实验的估计时间:90 分钟。

注意:每个练习都附带了一个初始解决方案。这些解决方案中有些代码片段是空的,我们将通过每个练习填写完整。因此,如果直接运行,初始解决方案将无法运行。

在每个练习中,您都可以找到 End 文件夹,其中包括完成练习后应该得到的解决方案。如果需要其他帮助来完成练习,您可以使用该解决方案作为指南。

               

下一步:

练习 1:创建和使用 ADO.NET 数据服务

               

练习 1:创建和使用 ADO.NET 数据服务

在本练习中,您将了解如何创建数据服务,并通过使用 .NET Data Services Client API 的现有 WPF 用户界面来使用此数据服务。

注意:要验证每个步骤是否正确执行,建议在每次任务结束时生成解决方案。

任务 1 –创建数据服务 Web 应用程序项目

在此任务中,您将创建一个 ASP.NET Web 应用程序项目,托管和公开数据服务。

1.            选择 Start | All Programs | Microsoft Visual Studio 2010 | Microsoft Visual Studio 2010 打开 Microsoft Visual Studio 2010。

打开 %TrainingKitInstallationFolder%\Labs\ADONetDataServices\Source\Ex01-CreatingDataServices\begin 中的以下解决方案文件之一:

a.            ADONETDataServiceSample.sln:用于练习 1 的空白解决方案。

2.            在 Solution Explorer 中,右键单击 ADONETDataServiceSample 解决方案,指向 Add,再选择 New Project。

3.            在 Add New Project 对话框中,选择 Visual C# | Web 项目类型。确保选中 .NET Framework 4.0 ,随后选择 ASP.NET Web Application  模板。

4.            您可将位置设置为 %TrainingKitInstallationFolder%\Labs\ADONetDataServices\Source\Ex01-CreatingDataServices\begin,也就是所提供的文件夹。

5.            将 Name 改为 WebSite,并单击 OK。

 

图 1

创建新网站

               

任务 2 –创建实体数据模型

在此任务中,您将创建连接可编程类和存储结构的映射规范。

实体数据模型 (EDM) 这种规范用于定义构建于实体框架基础之上的应用程序所用的数据。应用程序使用 EDM 定义设计架构中应用程序域的实体和关系。

1.            创建 AdventureWorks 实体数据模型。为此,在 Solution Explorer 中,右键单击 WebSite 项目,指向 Add 并单击 New Item。

2.            在 Add New Item 对话框中,选择 ADO.NET Entity Data Model。指定 Name 值为 AdventureWorks.edmx,随后单击 Add。

图 2

添加 ADO.NET 实体数据模型

3.            在 Entity Data Model Wizard 打开后,选择 Generate From Database 并单击 Next。

4.            指定数据库连接。为此,执行以下步骤:

a.            单击 New Connection

b.            在 Choose Data Source 对话框中,选择 Microsoft SQL Server 作为 Data Source,再单击 Continue。

c.             在 Connection Properties 对话框中,输入 NET40LABS 作为 Server Name,随后选择 AdventureWorksLT 数据库并单击 OK。

注意:NET40LABS 是本实验开始时运行 CheckDependencies.cmd 脚本所安装的数据库服务器的默认别名。

 

图 3

指定数据库连接

5.            返回 Entity Data Model Wizard,单击 Next。

6.            仅包含所有建议数据库对象中的 Tables 对象,默认保留 Model Namespace,清除所有复选框,单击 Finish。

 

图 4

选择模型中要包含的数据库对象

注意:有关信息请参见 ADO.NET Entity Framework。

               

任务 3 –创建数据服务

在此任务中,您将创建 ADO.NET 数据服务,使用 ADO.NET Entity Framework 指定的实体数据模型公开数据。

1.            添加数据服务。为此,在 Solution Explorer 中,右键单击 WebSite 项目,指向 Add 并单击 New Item。

2.            在 Add New Item 对话框中,选择 ADO.NET Data Service。指定 Name 值为 AdventureWorks.svc,单击 Add。

注意:如果 ADO.Net Data Service 项未出现在 Templates 对话框中,请参见以下链接:https://blogs.msdn.com/astoriateam/archive/2009/05/22/using-ado-net-data-services-v1-5-ctp1-with-visual-studio-2010-beta-1.aspx,其中包含在 VS2010 中添加 ADO.Net Data Service 项的步骤。

 

图 5

添加 ADO.Net 数据服务项

3.            从 WebSite 中删除以下引用:

◦              System.Data.Services

◦              System.Data.Services.Client

4.            找到类定义,并替换为如下代码。这将使服务使用实体框架数据模型作为数据源。

注意:Visual Studio 默认会为新服务打开代码文件。您还可以在 Solution Explorer 中找到 AdventureWorks.svc.cs 文件。

(代码片段 – 数据服务实验   – AdventureWorks 数据服务定义)

C#

public class AdventureWorks :DataService<AdventureWorksLTEntities>

5.       用如下内容替换 InitializeService 方法中的代码,启用与服务关联的实体数据模型中所有资源的读写访问权限:

(代码片段 – 数据服务实验 – AdventureWorks.svc InitializeService)

C#

public static void InitializeService(IDataServiceConfiguration2 config)

{

    config.SetEntitySetAccessRule("*", EntitySetRights.All);

}

注意:默认情况下,数据服务不会公开任何资源。需要首先启用资源访问权限,之后才能访问任何资源或关联。更多相关信息,请参见 IDataServiceConfiguration.SetEntitySetAccessRule 方法 (System.Data.Services)。

6.            将 Web 站点配置为使用 50000 端口。为此:

a.            在 Solution Explorer 中,右键单击 WebSite 项目,在上下文菜单中选择 Properties。

b.            在 Properties 页面中,打开 Web 选项卡。

c.             在 Servers 部分中,选择 Specific Port。

d.            将端口号设置为 50000。

e.            按 Ctrl + S 保存更改。

 

图 6

指定端口

               

任务 4 –通过 .NET Client API 使用服务

ADO.NET 数据服务包含内存占用最低的客户端库,为使用 .NET Framework 目标数据服务编写的应用程序提供了更为自然的编程模型。客户端库可以以 .NET 对象的形式返回结果。

在此任务中,您将通过 .NET 客户端库使用之前创建的数据服务。此库为使用 .NET Framework 和 Internet 数据服务的应用程序提供了编程模型。您将使用允许查询、添加、删除和更新数据库记录的 WPF 客户端应用程序与数据服务交互。这些操作是通过一个类执行的,此类作为访问数据服务的网关,并使用 ADO.NET Data Services Client API 返回实体对象。

1.            将 WPF 应用程序项目包含在解决方案之中。为此,在 Solution Explorer 中,右键单击 ADONETDataServiceSample 解决方案,指向 Add 并单击 Existing Project。浏览到 %TrainingKitInstallationFolder%\Labs\adoNetDataServices\Source\Assets\UserInterface,选择 UserInterface.csproj 并单击 Open。

2.            添加对 Microsoft.Data.Services.Client 程序集的引用,以便使用客户端库的 v1.5 CTP1 版本。为此,在 Solution Explorer 中,右键单击 UserInterface 项目,选择 Add Reference。在Add Reference 对话框窗口中,选择 Microsoft.Data.Services.Client.dll 程序集。单击 OK 添加引用。

注意:如果引用在 .NET 选项卡中不可用,请在 ADO.Net Data Services v1.5 CTP1 安装目录中浏览查找 DLL。更多相关信息请参见 https://blogs.msdn.com/astoriateam/archive/2009/05/22/using-ado-net-data-services-v1-5-ctp1-with-visual-studio-2010-beta-1.aspx。

图 7

添加 Microsoft.Data.Services.Client 引用

注意:由于 ADO.NET Data Services v1.5 CTP1 是一个独立版本,并非 v1 程序集的现成更新(System.Data.Services.dll 和 System.Data.Services.Client.dll)。因而此版本将与 v1 并存,允许选择为任一版本构建应用程序。

3.            使用 DataSvcUtil 工具创建表示数据服务定义中各实体的 .NET 对象。

注意:为了将数据服务中定义的各实体表示为客户端中的 .NET 对象,需要为各客户端应用程序定义相应的类。一种选择是手动定义类。另外一种更为自动化的选择是使用 ADO.NET 数据服务版本附带的 DataSvcUtil.exe 工具。

此工具将获取指向将生成的数据服务类型的基础 URL 参数。在本例中,该工具的访问地址是 https://localhost:50000/AdventureWorks.svc。

命令的输出是一个 C# 文件(可使用 /language:VB 开关生成 Visual Basic 类型的文件),其中包含数据服务中各实体类型的对应类。

a.            启动 WebSite 的一个实例,用于托管数据服务。为此,在 Solution Explorer 中,右键单击 WebSite 项目,指向 Debug,选择 Start New Instance。

注意: 如果出现 Debugging Not Enabled 对话框,选择 Modify the Web.config file to enable debugging 并单击 OK。

b.            以管理员身份打开命令提示符:选择Start | All Programs | Accessories | Command Prompt。

c.             浏览到 %ProgramFiles%\ADO.NET Data Services V1.5 CTP1\bin。

d.            运行以下命令:

Command

DataSvcUtil.exe /out:AdventureWorks.cs /uri:https://localhost:50000/AdventureWorks.svc /binding

注意:/binding 参数将生成代理代码,支持数据绑定接口。这是 v1.5 CTP1 版本中包含的新功能之一,但不会在本实验中出现。

更多相关信息请参见 ADO.NET Data Services v1.5 CTP1 -- 数据绑定概述。

e.            在 Visual Studio 中,按下 Shift + F5,或选择 Debug | Stop Debugging 停止应用程序。

f.             导入 DataSvcUtil 工具生成的 AdventureWorks.cs 文件。为此,在 Solution Explorer 中,右键单击 UserInterface 项目,指向 Add,再单击 Existing Item。浏览到 %ProgramFiles%\ADO.NET Data Services V1.5 CTP1\bin,选择  AdventureWorks.cs 文件,单击 Add。

注意:若在上述步骤中出现任何问题,您还可以在 %TrainingKitInstallationFolder%\Labs\ adoNetDataServices\Source\Assets 文件夹中找到 DataSvcUtil  工具生成的 AdventureWorks.cs 文件。

注意:如果您更倾向于使用 Visual Studio 提供的 Add Service Reference 选项来生产客户端类,那么就需要设置两个环境变量来支持 v1.5 CTP1 代码生成。为此,打开 Visual Studio Command Prompt (2010),执行如下命令:

- set dscodegen_15=1:    此环境变量将启动 v1.5 CTP1 代码生成。

- set dscodegen_databinding=1:   此环境变量为客户端类启用数据绑定接口支持。

随后,通过命令提示符启动一个新的 Visual Studio 实例,执行 devenv.exe。

4.            首先使用 ADO.NET Data Service URI Syntax 和 LINQ to ADO.NET Data Services 实现 ProductGateway 操作。在 Solution Explorer 中打开 ProductGateway.cs。为此,在 UserInterface 项目的 Gateway 文件夹下双击 ProductGateway.cs 文件。

5.            检索所有与使用 ADO.NET Data Service URI 语法作为参数传入的名称和类别匹配的产品。为此,使用以下内容替换 GetProducts 方法。

C#

public IList<Product> GetProducts(string productName, ProductCategory category)

{

    int categoryId = category.ProductCategoryID;

    IEnumerable<Product> products = this.context.Execute<Product>(

        new Uri(this.context.BaseUri.ToString() +

            "/ProductCategory(" + categoryId + ")/Product?$filter=indexof(Name,'" + productName + "') gt -1 or '' eq '" + productName + "'"));

    List<Product> productsSet = new List<Product>();

    foreach (Product p in products)

    {

        this.context.LoadProperty(p, "ProductCategory");

        productsSet.Add(p);

    }

    return productsSet;

}

注意:此查询选择所有属于指定类别的产品,还会执行筛选器检查产品名。此筛选器使用 indexof 函数,并使用 ADO.NET Data Services URI 语法的 gt(大于)、or 和 eq(等于)运算。

更多相关信息,请参见 用于寻址资源的 URI 格式(ADO.NET 数据服务框架)。

6.            使用 LINQ to ADO.NET Data Services 检索 ProductCategory 表中存储的所有产品类别。客户端库处理将 LINQ 语句映射到目标数据服务中 URI 的所有细节,并将指定资源作为 .NET 对象检索。为此,使用以下内容替换 GetCategories 方法。

(代码片段 – 数据服务实验 – ProductGateway GetCategories)

C#

public IList<ProductCategory> GetCategories()

{

    var productCategories = from c in this.context.ProductCategory

                            orderby c.Name

                            select c;

    return productCategories.ToList();

}

注意:此方法中使用的上下文对象是 AdventureWorksLTEntities 的一个实例。此类具有将 AdventureWorksLT 数据库的各表表示为实现 IQueryable 接口,并允许 LINQ 处理这些实体的 DataServiceQuery 类。

7.            从数据库中删除指定产品,随后调用 SaveChanges 方法来将更改提交到数据服务。为此,将以下代码添加到 DeleteProduct 方法中。

(代码片段 – 数据服务实验 – ProductGateway DeleteProduct)

C#

public void DeleteProduct(Product product)

{

    this.context.AttachTo("Product", product);

    this.context.DeleteObject(product);

    this.context.SaveChanges();

}

8.            更新产品属性,包含对象和单一值的关系。为此,将以下代码添加到 UpdateProduct 方法中。

注意:此代码使用关联的新 ProductCategory 对象接收产品,并将其存储在一个临时变量中。随后调用 LoadProperty 方法,它会重新从数据库载入 ProductCategory 对象,删除旧的绑定。最后,使用能够保存更改的上下文添加新的 ProductCategory 及其绑定。另外还要注意,上下文应首先附加对象,以对其执行操作。

(代码片段 – 数据服务实验 – ProductGateway UpdateProduct)

C#

public void UpdateProduct(Product product)

{

    ProductCategory newCategory = product.ProductCategory;

    this.context.AttachTo("Product", product);

    this.context.LoadProperty(product, "ProductCategory");

    if (newCategory.Name != product.ProductCategory.Name)

    {

        this.context.DeleteLink(product, "ProductCategory", product.ProductCategory);

        this.context.AttachTo("ProductCategory", newCategory);

        this.context.AddLink(product, "ProductCategory", newCategory);

    }

    this.context.UpdateObject(product);

    this.context.SaveChanges();

}

注意:在更新关联属性时,必须删除该属性的旧绑定,并创建新绑定,之后才能调用 UpdateObject 方法。

9.            将新产品添加到数据库中。为此,将以下代码添加到 AddProduct 方法中。

(代码片段 – 数据服务实验 – ProductGateway AddProduct)

C#

public void AddProduct(Product product)

{

    product.rowguid = Guid.NewGuid();

    this.context.AddObject("Product", product);

    product.ProductCategory.Product.Add(product);

    this.context.AttachTo("ProductCategory", product.ProductCategory);

    this.context.AddLink(product.ProductCategory, "Product", product);

    this.context.SaveChanges();

}

注意:在添加与另一个对象关联的对象时,您还必须使用 AddLink 方法绑定其对象属性。

               

下一步:

验证

               

验证

要验证是否正确执行了所有练习的步骤,执行以下步骤:

验证 1

在此验证中,您将使用 Web 浏览器来使用数据服务,并检查器 XML 响应。

注意:要试用数据服务,最简单的方法就是通过 Web 浏览器访问它。尽管这可能不是您最终使用数据服务的方法(更有可能的是通过一个程序与之交互),但确实是理解请求的工作方式、结果形式和其他与服务的实现有关的细节的便捷方法。

1.            要与数据服务交互,您需要启动 WebSite 项目的一个新实例。为此,在 Solution Explorer 中右键单击 WebSite 项目,指向 Debug,选择 Start New Instance。

注意: 如果出现 Debugging Not Enabled 对话框,选择 Modify the Web.config file to enable debugging 并单击 OK。

2.            浏览到 https://localhost:50000/AdventureWorks.svc。您将看到,数据服务的 XML 响应是一个实体集列表。实体数据模型 (EDM) 风格的实体集表示数据服务公开的数据库表。输出将类似于如下内容。

注意:默认返回的 XML 文档是一个 Atom 服务文档,因为数据服务使用的默认序列化是 Atom。

 

图 8

数据服务 XML 响应

3.            要浏览特定产品,在 Web 浏览器中使用如下地址 https://localhost:50000/AdventureWorks.svc/Product(680)(其中的 680 是产品 id)。将出现以下输出。

 

图 9

特定产品的数据服务 XML 响应

注意:如果浏览器显示一条消息表明无法显示源,请尝试关闭源阅读视图。在 Internet Explorer 中,此选项位于 Tools | Internet Options | Content 选项卡 | Feeds Section | Settings 之中,请清除 Turn on feed reading view 复选框。

4.            在 Visual Studio 中,按下 Shift + F5 停止调试。

注意:ADO.NET 数据服务框架实现了使用 URI 的数据寻址架构。它基于针对数据类型和数据集的实体数据模型 (EDM) 规范。要使用 URI 构建更复杂的服务请求,请参见使用 URL 的简单数据寻址架构。

               

验证 2

在此验证中,您将使用 WPF 应用程序,从数据库中检索产品列表,并执行产品细节的更新。

1.            将 UserInterface 项目设置为 StartUp 项目。为此,在 Solution Explorer 中,右键单击 UserInterface 项目,选择 Set as StartUp Project。

2.            按 F5 运行应用程序。将出现以下窗口。您可以通过此窗口列举、创建、更新和删除产品。

 

图 10

应用程序主窗口

3.            在 Product Category 组合框中选择 Brakes,并单击 Search。应用程序将从数据服务中检索 Brakes 类别中的所有产品,并将其显示在表格中。

 

图 11

Brakes 类别的产品

4.            编辑列表详细信息中的第一个产品。为此,双击产品列表中的第一个产品。

 

图 12

编辑产品细节

5.            将产品的颜色从 Silver 更改为 Gold,单击Save。应用程序将更新产品实体,记录将存储在数据库中。

 

图 13

更新后的产品

6.            您还可以尝试创建与删除操作。

7.            关闭应用程序,结束本次验证。

               

下一步:

练习 2:通过 ASP.NET AJAX API 使用 ADO.NET 数据服务

练习 2:通过 ASP.NET AJAX API 使用 ADO.NET 数据服务

ADO.NET 数据服务提供了客户端脚本类,用于简化 ASP.NET AJAX 应用程序与 ADO.NET 数据服务之间的交互。使用此类,您可以创建 Web 应用程序,通过 Web 站点上的数据服务与数据交互,还可在无需全部回发到 Web 服务器的前提下更新 Web 页面。

在此练习中,您将了解如何通过 ASP.NET AJAX 应用程序使用 ADO.NET 数据服务,利用此脚本类通过数据服务查询、删除或修改数据。

注意:要验证每个步骤是否正确执行,建议在每次任务结束时生成解决方案。

任务 1 –将客户端配置为使用数据服务

在此任务中,您将修改客户端的一些配置,从而允许您通过 ASP.NET AJAX API 使用数据服务。

1.            选择 Start | All Programs | Microsoft Visual Studio 2010 | Microsoft Visual Studio 2010,打开 Microsoft Visual Studio 2008。

2.            打开位于 %TrainingKitInstallationFolder%\Labs\AdoNetDataServices\Source\Ex02-ConsumingDataServicesUsingAspNetAjax\begin 文件夹中的 ADONETDataServiceUsingAJAX.sln。

3.            将数据服务客户端库添加到项目。为此,右键单击 WebSite 项目并选择 Add,再选择 Existing Item。在 Add Existing Item 对话框中,浏览到 %TrainingKitInstallationFolder%\Labs\AdoNetDataServices\Source\Assets 文件夹,并选择 DataService.js 文件。

注意:在过去的版本中,这个 AJAX 客户端库内嵌于 System.Web.Extensions 程序集之中。它是在 .NET 3.5 SP1 中移除的,因而成为了一个需要添加到项目中的外部文件。

为方便起见,我们已经将此文件随培训包一起打包,也可在此处下载。

4.            添加一个 ScriptManager 来管理 Microsoft ASP.NET AJAX 页面的客户端脚本。为此,将以下内容添加到 Default.aspx web 页面。

ASP.NET

<body>

<form id="form1" runat="server">

<asp:Button runat="server" ID="hiddenTargetControlForModalPopup"

Style="display:none" />

    <cc1:toolkitscriptmanager id="ToolkitScriptManager" runat="server">

       <Scripts>

         <asp:ScriptReference Path="~/DataService.js" />

       </Scripts>

   </cc1:toolkitscriptmanager>

<cc1:modalpopupextender runat="server" id="ModalPopupExtender1"

targetcontrolid="hiddenTargetControlForModalPopup"

popupcontrolid="pnlProductDetail" backgroundcssclass="modalBackground"

okcontrolid="btnSave" onokscript="doSaveAction()"

cancelcontrolid="btnCancel" dropshadow="true"

popupdraghandlecontrolid="pnlDrag"

behaviorid="programmaticModalPopupBehavior">

</cc1:modalpopupextender>

...

注意:在本例中,您将使用 ToolkitScriptManager,因为 Web 页面使用了 AJAXControlToolkit 中的一个控件。如果有一个页面未使用 AJAXControlToolkit 中的控件,那么您也可以定义一个常规 ScriptManager 控件。

5.            实现检索 DataService 类实例的方法。此类提供了可为与 ASP.NET AJAX 应用程序中的数据服务交互而调用的函数。为此,使用以下内容替换 getService() 函数(位于 WebSite 项目的 ProductGateway.js 文件中)。

JavaScript

function getService()

{

    return new Sys.Data.DataService("/AdventureWorks.svc");

}

               

任务 2 –创建查询以使用数据服务

在此任务中,您将使用一些数据服务查询字符串选项创建一个 REST 查询,调用 DataService 类的查询方法以检索部分产品,并将其显示在 Web 页面中。

1.            以 REST 形式编写查询。为此,将以下内容添加到 getProducts() 函数中(位于located in ProductGateway.js 文件之中)(以粗体显示的内容)。

JavaScript

function getProducts()

{         

var ServiceGateway = getService();

var categoryID = $get("cmbProductCategory").value;

var productName = $get("txtProductName").value;

if( categoryID )

    {

        var queryStr = "/ProductCategory(" + categoryID + ")/Product";

        if( productName )

        {

            queryStr += "&$filter=indexof(Name,'" + productName + "') gt -1 or '' eq '" + productName + "'";

        }

// TODO:调用数据服务

    }

else

    {

alert('Please select a category');      

    }

}

2.            调用数据服务类的查询方法,从数据服务中检索产品。为此,将以下内容添加到 getProducts() 函数中(以粗体显示)。

JavaScript

function getProducts()

{         

var ServiceGateway = getService();

var categoryID = $get("cmbProductCategory").value;

var productName = $get("txtProductName").value;

if( categoryID )

    {

var queryStr = "/ProductCategory(" + categoryID + ")/Product";

if( productName )

        {

queryStr += "&$filter=indexof(Name,'" + productName + "') gt -1 or '' eq '" + productName + "'";

        }

        ServiceGateway.query(queryStr, getProductsSuccess, genericFailure);

    }

else

    {

alert('Please select a category');      

    }       

}

注意:查询函数签名为:

           query:function(query, succeededCallback, failedCallback, userContext, webRequest)

参数详细信息如下:

query:查询的路径。

succeededCallback:成功完成操作时执行的回调。

failedCallback:未成功完成操作时执行的回调。

userContext:与该操作关联的上下文对象。

webRequest:用于该操作的 WebRequest 对象。

注意:请注意,ProductGateway.js 中的 getCategories() 函数也会进行查询,也会应用 orderby 查询选项:"/ProductCategory?$orderby=Name"。这将检索按 Name 属性排序的所有 ProductCategory 项。

               

任务 3 –使用数据服务插入实体

在此任务中,您将创建一个产品对象,此对象将发送给数据服务,以执行数据库插入操作。您还将实现一个回调函数,每次插入方法成功时,都将执行此函数。

1.            创建表示将发送给数据服务的新产品的对象。为此,将以下代码添加到 insertProduct() 函数中(位于 ProductGateway.js 文件内)。

JavaScript

function insertProduct()

{

var ServiceGateway = getService();

    var newProduct =

    {

        Name:$get("txtName").value,

        ProductNumber:$get("txtProductNumber").value,

        Color:$get("txtColor").value,

        StandardCost:$get("txtStandardCost").value,

        ListPrice:$get("txtListPrice").value,

        Size:$get("txtSize").value,

        Weight:$get("txtWeight").value,

        ProductCategory:{__metadata:{uri:"ProductCategory(" +

                         $get("cmbPDCategory").value + ")"}},

        SellStartDate:new Date(),

        SellEndDate:new Date(),

        ModifiedDate:new Date()

    };

// TODO:调用数据服务

   

}

2.            通过调用数据服务网关的插入方法插入新产品。为此,将以下代码添加到 insertProduct() 函数中。

注意:Product 实体具有一个 ProductCategory 外键,需要采用与其他属性不同的方式进行处理。除了设置外键值 ProductCategoryId, 之外,您还需要以 URI 的形式定义 ProductCategory 相关实体的指针:

           uri:"ProductCategory(" ProductCategoryId")"

必须将其在 __metadata 元素内发送给数据服务。

JavaScript

function insertProduct()

{

var ServiceGateway = getService();

var newProduct =

    {

Name:$get("txtName").value,

ProductNumber:$get("txtProductNumber").value,

Color:$get("txtColor")..value,

StandardCost:$get("txtStandardCost").value,

ListPrice:$get("txtListPrice").value,

Size:$get("txtSize").value,

Weight:$get("txtWeight").value,

ProductCategory:{__metadata:{uri:"ProductCategory(" +

$get("cmbPDCategory").value + ")"}},

SellStartDate:new Date(),

SellEndDate:new Date(),

ModifiedDate:new Date()

    };

    ServiceGateway.insert(newProduct, "/Product", insertProductSuccess, genericFailure);

}

注意:插入函数签名为:

           insert:function(item, resourceSetUri, succeededCallback, failedCallback, userContext, webRequest)

参数详细信息如下:

item:要插入的项。

resourceSetUri:要在其中插入项的资源集。

succeededCallback:成功完成操作时执行的回调。

failedCallback:未成功完成操作时执行的回调。

userContext:与该操作关联的上下文对象。

webRequest:用于该操作的 WebRequest 对象。

3.            实现在每次新产品插入成功时执行的函数。在本例中,您将使用新项刷新产品表格。为此,使用以下内容替换 insertProductSuccess 函数。

JavaScript

function insertProductSuccess(result, context, operation)

    getProducts();

}

               

任务 4 –使用数据服务更新实体

在此任务中,您将更改要发送到数据服务的现有产品,以更新数据库。

1.            获取要发送的完整对象,并更新其属性。为此,将以下内容添加到 updateProduct() 函数(位于 ProductGateway.js 文件中)。

JavaScript

function updateProduct()

{

var ServiceGateway = getService();

    updatedProduct = Records[$get("txtSelectedIndex").value];

    updatedProduct.Name = $get("txtName").value;

    updatedProduct.ProductNumber = $get("txtProductNumber").value;

    updatedProduct.Color = $get("txtColor").value;

    updatedProduct.StandardCost = $get("txtStandardCost").value;

    updatedProduct.ListPrice = $get("txtListPrice").value;

    updatedProduct.Size = $get("txtSize").value;

    updatedProduct.Weight = $get("txtWeight").value;

    updatedProduct.ModifiedDate = new Date();

// TODO:调用数据服务

}

注意:在本例中,您要将完整的对象发送给数据服务,有一些字段无需更新,将在事务中保持不变。此外还有另一种方法,您可仅发送需要更新的对象属性。

2.            通过调用数据服务网关的更新方法更新产品。为此,将以下代码添加到 updateProduct() 函数中。

JavaScript

function updateProduct()

{

var ServiceGateway = getService();

updatedProduct = Records[$get("txtSelectedIndex").value];

updatedProduct.Name = $get("txtName").value;

updatedProduct.ProductNumber = $get("txtProductNumber").value;

updatedProduct.Color = $get("txtColor").value;

updatedProduct.StandardCost = $get("txtStandardCost").value;

updatedProduct.ListPrice = $get("txtListPrice").value;

updatedProduct.Size = $get("txtSize").value;

updatedProduct.Weight = $get("txtWeight").value;

updatedProduct.ModifiedDate = new Date();

    ServiceGateway.update(updatedProduct, null, updateProductSuccess, genericFailure, updatedProduct);

}

注意:更新函数签名为:

           update:function(item, resourceUri, succeededCallback, failedCallback, userContext, webRequest)

参数详细信息如下:

item:要更新的项。

resourceUri:其中的项要更新的资源集。

succeededCallback:成功完成操作时执行的回调。

failedCallback:未成功完成操作时执行的回调。

userContext:与该操作关联的上下文对象。

webRequest:用于该操作的 WebRequest 对象。

               

任务 5 –使用数据服务删除实体

在此任务中,您将使用数据服务从数据库中删除产品。

1.            循环遍历产品列表,检索所有选定行,通过调用数据服务网关的删除方法来删除内容。为此,将以下内容添加到 deleteProduct() 函数(位于 ProductGateway.js 文件中)(以粗体显示的内容)。

JavaScript

function deleteProduct()

{

var ServiceGateway = getService();

    for (i=0;i<Records.length;i++)

    {       

        var chk = $get("chk_product_" + i);

        if (chk.checked)

        {

            var product = Records[i];

            ServiceGateway.remove(null, "/Product(" + product.ProductID + ")", deleteProductSuccess, genericFailure);

        }

    }

}

注意:remove 函数签名为:

           remove:function(item, resourceUri, succeededCallback, failedCallback, userContext, webRequest)

参数详细信息如下:

item:要删除的项。

resourceUri:要在其中删除项的资源集。

succeededCallback:成功完成操作时执行的回调。

failedCallback:未成功完成操作时执行的回调。

userContext:与该操作关联的上下文对象。

webRequest:用于该操作的 WebRequest 对象。

               

下一步:

验证

               

验证

要验证是否正确执行了所有练习的步骤,执行以下步骤。

在此验证中,您将利用一个 Web 页面来使用数据服务。您将通过数据服务 AJAX API 查询、插入、更新和删除产品。

1.            要与数据服务交互,您需要启动 WebSite 项目的一个新实例。为此,在 Solution Explorer 中右键单击 WebSite 项目,指向 Debug,选择 Start New Instance。应显示如下 Web 页面:

注意:如果在使用 Internet Explorer 8 时解决方案抛出 JScript 错误,请忽略异常,并打开 Internet Explorer 8 Compatibility View ( )。

如果出现 Debugging Not Enabled 对话框,选择 Modify the Web.config file to enable debugging 并单击 OK。

 

图 14

启动 WebSite 项目的新实例

2.            查询对应于 Brakes 类别的产品。为此,在 Product Category 下拉菜单中选择 Brakes 值,并单击 Search。应显示如下页面:

 

图 15

查询产品

3.            插入新产品。为此,单击 New Product ,在模型弹出扩展器中键入以下数据。

a.            Product Number:ABS-9444

b.            Color:Black

c.             Standard Cost: 150

d.            Size: 2

e.            Name:ABS Brakes

f.             Category:Brakes

g.            List Price: 150

h.            Weight: 320

    

图 16

插入新产品

单击 Save 调用数据服务并插入产品。请注意产品列表的刷新方式,以便查看新插入的产品。

 

图 17

检索插入的产品

4.            更新最近插入的产品。为此,单击您在之前的实验步骤中插入的 ABS Brakes 产品行,等待弹出模型扩展器显示选定信息。更新以下字段:

a.            Product Number:RIM-9444

b.            Name:Rim Brakes

    

图 18

更新产品

单击 Save 调用数据服务并更新产品。您应看到在产品列表刷新时,最近更新的产品已经更新。

 

图 19

显示更新的产品

5.            删除插入的产品。为此,选中要删除的行的 Select 复选框,并单击 Delete Product。

 

图 20

删除产品

在删除之后,产品列表将刷新,产品将消失。

               

下一步:

练习 3:使用服务操作和侦听器扩展数据服务

               

练习 3:使用服务操作和侦听器扩展数据服务

有时候,您需要添加数据验证规则或自定义行为。在此练习中,您将使用服务侦听器添加验证支持,使用服务操作执行自定义查询。

本练习中使用的场景与上一个练习中的场景极为相似。您将检索产品列表,但这一次将使用服务操作,而不是 REST 查询,随后添加服务侦听器来升级创建和更新产品功能。

任务 1 –创建服务操作

在此任务中,您将创建一个服务操作,检索产品列表、添加访问规则,并通过 JS 脚本进行调用。

1.            选择Start | All Programs | Microsoft Visual Studio 2010 | Microsoft Visual Studio 2010,打开 Microsoft Visual Studio 2010。

2.            打开 DataServiceOperationsAndInterceptors.sln 解决方案文件。为此,在 Visual Studio 中选择 File | Open | Project/Solution,浏览到 %TrainingKitInstallationFolder%\Labs\adoNetDataServices\Source\Ex03-ServiceInterceptors\begin 文件夹,选择 DataServiceOperationsAndInterceptors.sln 文件并单击 Open。

3.            打开 AdventureWorks.svc.cs 文件。为此,在 Solution Explorer 中双击 AdventureWorks.svc.cs 文件。

 

图 21

打开 AdventureWorks.svc.cs 文件

4.            添加服务操作来检索产品。为此,在 AdventureWorks.svc.cs 文件中添加如下方法。

注意:此方法具有一个 WebGet 属性,因为此 API 是基于 WCF 工作的。服务操作将接收两个参数,返回一个 IQueryable 接口。该方法使用 CurrentDataSource 属性作为数据源,执行 LINQ 查询,若参数不是空字符串,则检索指定类别内带有指定产品名的产品。

(代码片段 – 数据服务侦听器实验 – 服务操作)

C#

[WebGet]

public IQueryable<Product> GetProducts(string productName, int productCategoryId)

{

    return from p in this.CurrentDataSource.Product

           where p.ProductCategory.ProductCategoryID == productCategoryId

           && (p.Name == productName || String.IsNullOrEmpty(productName))

           select p;

}

注意:要访问数据,您应使用服务的 CurrentDataSource 属性,这是在声明数据服务类时接收到的泛型类型。由于在接收时作为泛型类型,因而服务将通过 WCF 注册序列化程序。如果您使用不同的数据源,应答将无法序列化。

注意:通过返回查询而非最终数据,数据服务引擎就能为查询应用其他运算符(如排序或筛选)。如果您希望避免这种情况,请返回 IEnumerable 接口。

5.            要能够使用服务操作,您需要指定访问规则。为此,请将以下粗体显示的代码插入 InitializeService 方法(位于 AdventureWorks.svc.cs 文件中)。

(代码片段 – 数据服务侦听器实验 – 访问规则)

C#

public static void InitializeService(IDataServiceConfiguration2 config)

{

config.SetEntitySetAccessRule("*", EntitySetRights.All);

   config.SetServiceOperationAccessRule("GetProducts", ServiceOperationRights.All);

}

将服务操作 GetProducts 配置为具有 All 访问权限。

注意:其他可能的权限还包括 AllRead、None、ReadMultiple 和 ReadSingle。

6.            添加 JavaScript 代码来调用此服务操作。为此,在 Solution Explorer 中双击 ProductGateway.js,删除 getProducts 函数内的代码,并将以下代码添加到此函数中。

JavaScript

function getProducts()

{      

    var ServiceGateway = getService();

    var sb = new Sys.StringBuilder();

    sb.append("GetProducts?");

    sb.append("productName=");

    sb.append($get("txtProductName").value);

    sb.append("&");

    sb.append("productCategoryId=");

    sb.append($get("cmbProductCategory").value);

   

    ServiceGateway.query(sb.toString(), getProductsSuccess, genericFailure);

}

注意:此方法获取一个服务网关,它是 Sys.Data.DataService 类的一个实例,随后生成字符串,调用 GetProducts 服务操作,在所编码的查询字符串 URL 中添加两个参数。最后,它使用查询字符串和两个回调函数执行查询。一个用于在成功时调用,另一个用于在失败时调用。

7.            要验证此任务,在 solution explorer 中按 F5 来运行解决方案。随后在 Product Category 下拉列表中选择 Brakes 类别,并单击 Search。您应该会看到类似于下图的输出。

 

图 22

使用服务操作

注意:如果在使用 Internet Explorer 8 时解决方案抛出 JScript 错误,请忽略异常,并打开 Internet Explorer 8 Compatibility View ( )。

8.            关闭 web 浏览器终止应用程序。

               

任务 2 –创建服务侦听器

在此任务中,您将创建一个服务侦听器,添加必要的数据库字段。它将为每个要保存在数据库中的更新产品添加一个行 GUID。

1.            添加一个变更侦听器,为所创建的每个新产品添加一个行 GUID。为此,在 AdventureWorks.svc.cs 文件中,将以下方法添加到 GetProducts 方法之下。

(代码片段 – 数据服务侦听器实验 – 变更侦听器添加 rowGuid)

C#

[ChangeInterceptor("Product")]

public void OnChangeProduct(Product product, UpdateOperations action)

{          

    if (action == UpdateOperations.Add)

    {

        product.rowguid = Guid.NewGuid();

    }

}

注意:此方法具有一个 ChangeInterceptor 属性,以 Product 字符串作为参数,指出它将侦听 Product 实体的所有更改。服务侦听器接收两个参数,一个是将要侦听的实体,另一个是所执行的变更操作。无返回值。此方法将检查操作,如果是 Add 操作,将为当前 Product 实体添加一个新的 GUID。

2.            现在,您将在之前插入的变更侦听器中添加验证逻辑,这将应用于新的和更新的实体。您还将为所添加或更新的每个产品添加一个修改日期。为此,将以下代码添加到 OnChangeProduct 方法中。

(代码片段 – 数据服务侦听器实验 – 变更侦听器产品颜色验证)

C#

[ChangeInterceptor("Product")]

public void OnChangeProduct(Product product, UpdateOperations action)

{

    if (action == UpdateOperations.Add || action == UpdateOperations.Change)

    {

        if (String.IsNullOrEmpty(product.Color))

            throw new DataServiceException("Product must have a color specified");

        DateTime fakeToday = new DateTime(2009, 03, 26);

        product.ModifiedDate = fakeToday;

    }

if (action == UpdateOperations.Add)

    {

product.rowguid = Guid.NewGuid();

    }

}

注意:需要验证 Product 实体中的 Color 属性不能为空或空字符串。如果存在空值,则将抛出异常,并中止操作。该方法还会为每一个添加或修改的产品添加一个修改日期。

3.            要验证上述步骤,请按 F5 键运行解决方案。

4.            编辑现有产品,检查颜色验证。为此,在 Product Category 下拉菜单中选择 Brakes,并单击 Search。

 

图 23

列举制动器产品

5.            单击上图中的第一行,即 Name 为 Rear Brakes 的行。您应看到弹出如下窗口。

 

图 24

编辑 Rear Brakes

6.            清空 Color 字段,单击 Save。将出现以下警告。

 

图 25

更新方法失败

注意:警告显示了作为脚本中的回调事件处理程序的参数接收的错误消息。

在 Windows 7 中使用 Internet Explorer 8 时,JavaScript 可能不会捕捉到异常,但将在 Visual Studio 中显示为未处理的异常。

7.       在表格中编辑同一个现有制动器产品。为此,单击所列出的第一行。将 Size 设置为 3、将 Color 设置为 Red。单击 Save 提交更改。请注意,表格已更新。修改日期为 2009-03-26,此值是在服务侦听器中设置的。

 

图 26

更新产品

8.            关闭 web 浏览器终止应用程序。

               

任务 3 –使用服务侦听器添加自定义查询约束

在此任务中,您将添加查询 Product Category 实体时的自定义约束。检索产品类别,以填充 Product Category 下拉列表。

1.            在 AdventureWorks.svc.cs 文件中添加如下 using 指令

C#

using System.Linq.Expressions;

2.            添加查询侦听器,仅检索以字母‘B 开头的类别。为此,将以下代码添加到 AdventureWorks.svc.cs 文件中的 OnChangeProduct 方法之下。

(代码片段 – 数据服务侦听器实验 – 查询侦听器 ProductCategory)

C#

[QueryInterceptor("ProductCategory")]

public Expression<Func<ProductCategory, bool>> OnQueryProductCategory()

{

    return (pc) => pc.Name.StartsWith("B");

}

 

注意:此方法具有一个 QueryInterceptor 属性,以 ProductCategory 字符串作为参数,指出它将侦听 ProductCategory 实体的所有查询。此方法不接受参数,返回一个将由 ADO.NET 数据服务引擎计算的 Expression<Func<EntityType, bool>>(

其中 EntityType 是所侦听的实体的实际类型)。

3.            要验证此步骤,请按 F5 键运行解决方案。

4.            展开 Product Category 列表,确认仅显示了以字母‘B 开头的产品类别。

 

图 27

筛选产品类别

5.            关闭 web 浏览器终止应用程序。

               

下一步:

练习 4:添加带有行计数的客户端分页

练习 4:添加带有行计数的客户端分页

在此练习中,您将了解如何为项列表添加客户端分页。

客户端的不同之处在于负责以分页的方式检索项的服务器驱动的分页。在客户端分页中,客户端应用程序控制从服务器检索的记录数量,控制每次请求应跳过多少记录,将服务器的责任限制为检索客户端查询所请求的记录。另一方面,服务器端分页完全由服务器应用程序完成,它限制为每次请求返回的记录数量,并为客户端提供一个 URI,使之能够了解如何继续检索后续页面。

注意:关于服务器驱动的分页的更多相关信息,请参见 https://blogs.msdn.com/astoriateam/archive/2009/03/19/ado-net-data-services-v1-5-ctp1-server-driven-paging.aspx。

任务 1 –调用计数方法

在此任务中,您将为 ProductGateway 添加一个新方法,检索与用户输入的标准匹配的产品数量,并使用现在支持的 Count 扩展方法。

1.            打开解决方案文件 DataServicesRowCount.sln ,位于 ..\AdoNetDataServices\Source\Ex04-RowCount\begin 文件夹中。解决方案文件包含在 ADO.NET 数据服务实验的练习 1 中创建的 UserInterface 和 WebSite 项目。

注意:此外,您也可以继续使用在完成本实验的练习 1 之后获得的解决方案。

2.            导航到 Gateways 文件夹下 UserInterface 项目中的 IProductGateway.cs 文件。

 

图 28

IProductGateway.cs 文件

3.            添加一个新方法,检索与用户输入的标准匹配的产品数量。为此,将以下代码添加到 IProductGateway 接口中。

C#

int GetProductsCount(string productName, ProductCategory category);

4.            在 ProductGateway 类上实现新方法。为此,打开 ProductGateway.cs 文件并添加如下代码。

注意:在 ADO.NET Data Services 的早期版本中,在 DataServiceQuery 对象上调用 Count 方法会导致抛出 NotSupportedException 异常。自 1.5 CTP1 版本之后,Count 和 LongCount 是在相应的实体集上实现的,并映射到 $count 部分。有必要强调,在本例中,未从数据库检索任何实体集,仅检索了数据库引擎处理的计数操作。

在过去的版本中,客户端分页是通过检索整个集、在所得到的枚举上调用 Count 方法实现的。这种方法的成本极为高昂,因为需要从数据库检索整个集,以便直接对枚举执行 Count 操作,若存在更改,此方法的性能比数据库引擎执行的 Count 操作低得多。

(代码片段 – 数据服务实验 – ProductGateway GetProductsCount 方法)

C#

public int GetProductsCount(string productName, ProductCategory category)

{

    var query = (DataServiceQuery<Product>)

        (from p in this.context.CreateQuery<Product>("Product")

         where ((p.Name.IndexOf(productName) > -1 || string.IsNullOrEmpty(productName)) &&

                 p.ProductCategory.ProductCategoryID == category.ProductCategoryID)

         select p);

    return query.Count();

}

注意:您可能已经注意到,GetProductsCount 方法管理查询的方式不同于 GetProducts 方法,使用 LINQ 查询检索产品集,而非直接创建查询 URI。可使用任何一种方法完成查询,ADO.NET 数据服务框架会将 LINQ 查询转换成 URI。

               

任务 2 –在用户界面上显示产品计数

在此任务中,您将为产品列表窗口添加一个 TextBlock,显示使用之前的任务中介绍的方法创建的产品计数。

1.            为 ProductList 窗口添加一个新的 TextBlock,显示匹配筛选标准的产品计数。为此,使用 XML 编辑器打开 ProductList.xaml 文件,在 <ListView> 控件下添加如下突出显示的代码。

XAML

...

<GridViewColumn Header="Weight"

DisplayMemberBinding="{Binding Path=Weight}" />

</GridView>

</ListView.View>

</ListView>

        <TextBlock Name="TotalProductsCountLabel" Grid.Row="3" Margin="15,0,16,5" VerticalAlignment="Center" HorizontalAlignment="Left" />

<Button Grid.Row="3" HorizontalAlignment="Right" Margin="0,0,120,10" x:Name="btnNewProduct" Click="BtnNewProduct_Click" VerticalAlignment="Bottom" d:LayoutOverrides="Height" Width="100" Content="New Product"/>

<Button HorizontalAlignment="Right" Margin="0,0,10,10" x:Name="btnDeleteProduct" Width="100" Grid.Row="3" Click="BtnDeleteProduct_Click" VerticalAlignment="Bottom" Content="Delete Product"/>

</Grid>

...

2.       添加一个新字段来存储产品计数。为此,打开 ProductList.xaml.cs 文件,在 ProductList 类中添加一个新字段,代码如下。

C#

private int productSetSize;

3.            添加一个新方法,用于检索产品计数,并将其显示在新的 TextBlock 中。为此,为 ProductList 类添加如下方法。

(代码片段 – 数据服务实验 – ProductList RecalculateProductsSetSize 方法)

C#

private void RecalculateProductsSetSize()

{

    ProductGateway gateway = new ProductGateway();

    this.productSetSize = gateway.GetProductsCount(NameTextBox.Text, CategoryComboBox.SelectedItem as ProductCategory);

    this.TotalProductsCountLabel.Text = string.Format(CultureInfo.CurrentUICulture, "Total Products in Category:{0}", this.productSetSize);

}

4.            为新方法添加调用。为此,将如下代码添加到现有 BtnSearch_Click, BtnDeleteProduct_Click 和 Window_Closed 方法中。

C#

private void BtnSearch_Click(object sender, RoutedEventArgs e)

{

    this.RecalculateProductsSetSize();

this.BindProducts();           

}

private void BtnDeleteProduct_Click(object sender, RoutedEventArgs e)

{

Product p = ProductsListView.SelectedItem as Product;

if (p != null)

    {

ProductGateway gateway = new ProductGateway();

gateway.DeleteProduct(p);

this.BindProducts();

        this.RecalculateProductsSetSize();

    }

}

private void Window_Closed(object sender, EventArgs e)

{

this.BindProducts();

    this.RecalculateProductsSetSize();

}

5.            验证产品计数是否显示在 UI 上。为此,将 UserInterface 设置为 StartUp Project,按 F5 键运行应用程序。在 Product Category 组合框中选择一个类别,单击 Search。您应该会看到类似于下图的画面。

  图 29

正在运行的带有产品计数标签的应用程序

               

任务 3 - 为现有产品列表查询添加分页

在此任务中,您将修改检索产品列表的方法,使之支持分页。

1.            为 IProductGateway 接口中的现有 GetProducts 方法添加 pageSize 和 number 参数。为此,导航到 IProductGateway.cs 文件,使用如下代码替换 GetProducts 方法声明。

(代码片段 – 数据服务实验 – IProductGateway GetProducts 方法)

C#

IList<Product> GetProducts(string productName, ProductCategory category, int pageSize, int pageNumber);

2.            为 ProductGateway 类中的 GetProducts 方法的现有实现添加 pageSize 和 number 参数。为此,使用如下代码替换当前方法签名。

C#

public IList<Product> GetProducts(string productName, ProductCategory category, int pageSize, int pageNumber)

3.            在查询中使用页面大小和数量参数。为此,将 ProductGateway 类中的 GetProducts 方法实现更改如下。

C#

public IList<Product> GetProducts(string productName, ProductCategory category, int pageSize, int pageNumber)

{

int categoryId = category.ProductCategoryID;

IEnumerable<Product> products = this.context.Execute<Product>(

        new Uri(this.context.BaseUri.ToString() +

            "/ProductCategory(" + categoryId + ")/Product?$filter=indexof(Name,'" + productName + "') gt -1 or '' eq '" + productName + "'&$top=" + pageSize + "&$skip=" + pageSize * pageNumber));

List<Product> productsSet = new List<Product>();

foreach (Product p in products)

    {

this.context.LoadProperty(p, "ProductCategory");

productsSet.Add(p);

    }

return productsSet;

}

注意:分页是通过设置 $top 和 $skip 查询字符串选项实现的。在设置 $top=n 选项时,所返回的实体数量限于前 n 行,若使用 $skip=m 选项,将导致结果集中的前 m 条记录被跳过。

               

任务 4 - 更新用户界面以显示分页列表视图

在此任务中,您将添加用户界面控件,允许用户浏览产品列表页面。

1.            在ProductList.xaml 中为根 Grid 添加新行。

XAML

<Grid x:Name="LayoutRoot">

<Grid.RowDefinitions>

<RowDefinition Height="55"/>

<RowDefinition Height="90"/>

<RowDefinition Height="*"/>

<RowDefinition Height="35"/>

      <RowDefinition Height="35"/>

</Grid.RowDefinitions>

...

2.            添加 Previous 和 Next 控件,以允许用户浏览产品列表页面,并定位到新行。为此,使用 XML Editor 打开 ProductList.xaml 文件,在 TextBlock 控件下添加如下突出显示的代码。

XAML

...

<TextBlock Name="TotalProductsCountLabel" Grid.Row="3" Margin="15,0,16,5" VerticalAlignment="Center" HorizontalAlignment="Left" />

<StackPanel Orientation="Horizontal" Grid.Row="4" Height="23" Margin="0,0,16,5" HorizontalAlignment="Right">

    <Button Name="PreviousPageButton" Width="75" Click="previousPageButton_Click">Previous</Button>

    <TextBlock Name="CurrentPageLabel" Width="90" Margin="15,0,15,0" VerticalAlignment="Center" Text="Current Page: 1" />

    <Button Name="NextPageButton" Width="75" Click="nextPageButton_Click">Next</Button>

</StackPanel>

...

3.            在 ProductList 类中添加新常量以修正页面大小。为此,将以下代码添加到 ProductList.xaml.cs 文件中。

C#

private const int PageSize = 8;

注意:此练习的页面大小是随机设置的,可任意更改以验证不同的分页配置。

4.            在 ProductList 类中添加新字段以存储当前页面。为此,添加以下代码。

C#

private int currentPageNumber = 0;

5.            在 ProductList 类中添加只读属性以计算页面总数。为此,添加以下代码。

6.           

(代码片段 – 数据服务实验 – ProductList TotalPages 属性)

C#

private int TotalPages

{

    get

    {

        return (this.productSetSize / PageSize) + (this.productSetSize % PageSize > 0 ? 1 : 0);

    }

}

7.            为新按钮的 Click 事件添加处理程序方法。为此,将以下代码添加到 ProductList 类中。

(代码片段 – 数据服务实验 – ProductList 导航按钮处理程序)

C#

private void previousPageButton_Click(object sender, RoutedEventArgs e)

{

    this.currentPageNumber -= 1;

    this.BindProducts();

}

private void nextPageButton_Click(object sender, RoutedEventArgs e)

{

    this.currentPageNumber += 1;

    this.BindProducts();

}

8.            添加逻辑来处理根据当前页面启用或禁用浏览按钮的情况。为此,为 ProductList 类添加如下方法。

(代码片段 – 数据服务实验 – ProductList UpdateNavigationButtons 方法)

C#

private void UpdateNavigationButtons()

{

    this.PreviousPageButton.IsEnabled = (this.currentPageNumber > 0);

   

    this.CurrentPageLabel.Text = string.Format(CultureInfo.CurrentUICulture, "Current Page:{0}", this.currentPageNumber + 1);

    this.NextPageButton.IsEnabled = this.currentPageNumber < (this.TotalPages - 1);

}

9.            更新 ProductList 类中的 BindProducts 方法,调用新的 GetProducts 和 UpdateNavigationButtons 方法。为此,使用如下突出显示的代码替换 BindProducts 方法的当前实现。

C#

private void BindProducts()

{

if (CategoryComboBox.SelectedIndex > -1)

    {

ProductGateway gateway = new ProductGateway();

        ProductsListView.ItemsSource = gateway.GetProducts(NameTextBox.Text, CategoryComboBox.SelectedItem as ProductCategory, PageSize, this.currentPageNumber);

        this.UpdateNavigationButtons();

    }

}

10.          在 Window_Loaded 处理程序上调用 UpdateNavigationButtons 方法,设置浏览按钮的初始状态,如下所示。

C#

private void Window_Loaded(object sender, RoutedEventArgs e)

{

this.BindCategories();

this.UpdateNavigationButtons();

}

11.          添加代码以便在新搜索完成时重置当前页面字段。为此,将以下代码添加到 BtnSearch_Click 方法中。

C#

private void BtnSearch_Click(object sender, RoutedEventArgs e)

{

    this.currentPageNumber = 0;

this.RecalculateProductsSetSize();

this.BindProducts();           

}

12.          调用 ProductView 类中的新 GetProducts 方法。为此,在 Solution Explorer 中双击 ProductView.xaml.cs,将如下突出显示的代码添加到 UpdateProduct 方法中。

C#

public void UpdateProduct(Product product)

{

ProductGateway gateway = new ProductGateway();

    this.Product = gateway.GetProducts(product.Name, product.ProductCategory,1,0)[0];

this.FormCreateMode = false;

this.Title = "Edit " + product.Name;

}

注意:在实际应用程序中,在查询一个产品时要创建一个新方法。但为了保持练习的代码简单,我们在这里分别使用大小为 1 和 0 页面(其中 0 表示首个页面的编号)调用了 GetProducts 方法,并获取所返回的集的第一个元素。

               

下一步:

验证

               

验证

在此验证中,您将了解分页的工作原理,了解如何允许用户浏览匹配用户输入的搜索条件的产品列表。您还会看到在添加和删除产品时,产品计数标签和浏览按钮的行为。

1.            启动 UserInterface 项目的一个新实例。为此,在 Solution Explore 中右键单击 UserInterface 项目,指向 Debug,选择 Start New Instance。

 

图 30

查看应用程序主窗口

2.            选择包含几种产品的一个类别(即 Mountain Bikes),单击 Search。

3.            验证产品总数标签是否出现,以及是否显示了属于选定类别的产品数量。

 

图 31

查看类别中的总产品量

4.            单击 Next 和 Previous 按钮,验证浏览功能是否正常。

5.            验证添加新产品页面时计数标签是否正确更新。为此,单击 New Product,完成必填字段,单击 Save。

注意:应用程序未验证用户输入,因而请确保键入一致的值,特别是日期(例如:1/1/2009 12:00:00 AM)。

 

图 32

产品列表的第五页

6.       验证在删除产品时页面和计数标签是否正确更新。为此,删除新创建的产品,方法是从列表中选择此产品,并单击 Delete Product。

               

下一步:

总结

               

总结

在本实验中,您通过 .NET API 和 AJAX API 创建和使用了 ADO.NET 数据服务。您还为 ADO.NET 数据服务添加了服务操作和侦听器。此外,您还了解了 ADO.NET Data Services v1.5 CTP1 版本中全新的行计数功能。