.NET 客户端库(ADO.NET 数据服务框架)

ADO.NET 数据服务包含使用 .NET Framework 和 ADO.NET 数据服务的应用程序的 .NET 客户端库。使用客户端库的应用程序可将来自数据服务的结果用作 .NET 对象。关联遍历由公共语言运行库 (CLR) 对象管理。

.NET 客户端库使用 HTTP 和 AtomPub 格式。此客户端库适合于企业网络和 Internet 环境。所需的全部工作只是建立与数据服务的 HTTP 级别的连接(直接连接或通过代理连接)。

System.Data.Services.Client

若要使用客户端库,请向项目中添加对 System.Data.Services.Client 程序集的引用。可以在任何项目类型(包括 Windows 窗体、Windows Presentation Foundation 和网站项目)中使用客户端库。

客户端库的两个主要构造分别为 DataServiceContext 类和 DataServiceQuery 类。DataServiceContext 表示具有指定数据服务的运行库上下文。虽然数据服务是无状态的,但上下文并非如此,各个交互之间将保持客户端的状态,以支持类似更改管理这样的功能。

DataServiceQuery 类表示针对使用 ADO.NET 数据服务 URI 语法指定的存储的查询。若要执行查询并获取 .NET 对象形式的结果,请枚举查询对象,例如,通过使用 C# 中的 foreach 构造或 Visual Basic 中的 For Each

若要将数据服务中定义的每个实体表示为 .NET 对象,则需要为客户端应用程序定义相应的类。既可以选择手动定义这些类,也可以选择使用下一个标题中介绍的 DataSvcUtil.exe 工具来定义这些类。

下面的示例演示 Address 类的手写定义,此类基于 SQL Server 2005 附带的 AdventureWorks 示例数据库中的数据。此示例使用 Address 实体和一小段用于执行针对服务的查询的代码。返回的 Address 的属性将作为输出显示。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Data.Services.Client;
    

namespace DataServiceClient
{

    public class Address
    {
        public int AddressID { get; set; }
        public string AddressLine1 { get; set; }
        public string AddressLine2 { get; set; }
        public string City { get; set; }
        public DateTime ModifiedDate { get; set; }
        public string PostalCode { get; set; }
        public Guid rowguid { get; set; }       
        public int StateProvinceID { get; set; }

    }

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }       


        private void button_Click(object sender, EventArgs e)
        {
            DataServiceContext ctx = new
                 DataServiceContext(new Uri("https://localhost:1492/AdvWksSales.svc/"));


          // This example expects the user to enter
             // an integer representing AddressID in textBox1.

             DataServiceQuery<Address> query =
                 ctx.CreateQuery<Address>(
                   "SalesOrderHeader(45678)/Address");

             StringBuilder output = new StringBuilder();
            
             foreach (Address address in query)
             {
                output.Append("Id: " + address.AddressID +
                        " Line 1: " + address.AddressLine1 + "\r\n");
                
             }

            richTextBox1.Text = output.ToString();
            


        }

输出如下所示。

Id: 70 Address Line1: 1792 Belmont Rd. City: Monroe

使用 DataSvcUtil 工具

虽然手动编写类的方法适用于少数类型,但当数据服务架构更为复杂时,要维护的类的数量和大小也会增加,以致难以通过手动方式进行维护。更好的选择是使用 ADO.NET 数据服务附带的名为 DataSvcUtil.exe 的代码生成工具。此工具可从数据服务定义生成 .NET 类。

DataSvcUtil.exe 位于目录 \WINDOWS\Microsoft.NET\Framework\v3.5\ 中。命令行采用以下参数:指向要为其生成类型的数据服务的基 URL。例如,如果 Northwind 服务在 Visual Studio 开发服务器上的 https://localhost:1234/Northwind.svc 位置运行,则用于生成类的命令行是:

C:\Program Files\Microsoft Visual Studio 9.0\VC>"C:\WINDOWS\Microsoft.NET\Framework\v3.5\DataSvcUtil.exe"
 /out:c:\northwind.cs /uri:https://localhost:1365/Northwind.svc

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

生成的类包含表示基元值的成员和通过对象模型帮助导航的关联。

LINQ to ADO.NET 数据服务

除了支持通过调用“Microsoft 数据 Web 客户端”标题下显示的 DataServiceContext.CreateQuery 来查询数据服务之外,.NET 客户端库还支持使用语言集成查询 (LINQ) 进行数据服务查询。有关 LINQ 的更多信息,请参见 LINQ to Entities。客户端库对将 LINQ 语句映射到目标数据服务中的 URI 和将指定资源检索为 .NET 对象的细节进行处理。下面的示例演示如何使用按照公司名称排序的返回结果集检索位于 London(伦敦)市的所有客户。

using System;
using System.Data.Services.Client;
using System.Linq;
using NorthwindModel;

namespace TestApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            NorthwindEntities ctx = new 
            NorthwindEntities(
              new Uri("https://localhost:1365/Northwind.svc"));

            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);
            }
        }
    }
}
Note注意

可用 LINQ 语法表达的查询集的范围比可用数据服务使用的基于具象状态传输 (REST) 的 URI 语法表达的查询集的范围要大得多。如果无法将查询映射到目标数据服务中的 URI,则将引发异常。有关 REST 的更多信息,请参见 REST 服务和语义(ADO.NET 数据服务框架)

Note注意

此标题及其后面两个标题中的示例使用了 Northwind 示例数据库。可以从以下网址下载 Northwind 示例:https://go.microsoft.com/fwlink/?linkid=24758

关联

对象之间的关联由 DataServiceContext 类跟踪和管理。可以积极或根据需要加载关联对象。Querying Data as Objects and Shaping Query Results(将数据作为对象查询并形成查询结果)下的 ADO.NET Entity Framework(ADO.NET 实体框架)文档中对积极加载和迟缓加载进行了讨论。使用统一的 URI 对数据进行寻址的简单方案(ADO.NET 数据服务框架)中讨论了 URL 格式。

对于根据需要加载关联实体,请使用 DataServiceContext 类的 LoadProperty 方法。下面的示例演示如何延迟加载与 Category 实体关联的 Product 实体。

using System;
using System.Data.Services.Client;
using NorthwindModel;

namespace TestApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            DataServiceContext ctx = new
              DataServiceContext(
                  new Uri("https://localhost:1365/Northwind.svc"));

            DataServiceQuery<Categories> categories = 
                       ctx.CreateQuery<Categories>("/Categories");
            
            foreach (Categories c in categories)
            {
                 Console.WriteLine(c.CategoryName);

                 ctx.LoadProperty(c, "Products");

                 foreach (Products p in c.Products)
                 {
                    Console.WriteLine("\t" + p.ProductName);
                 }
              }
            }
        }
    }
}

在某些情况下,您可能需要关联对象并希望避免因检索这些对象的额外请求而导致的延迟。在此情况下,可以在 URL 中使用 expand 选项。客户端库识别出结果包含顶级实体和关联实体,并将所有实体具体化为一个对象图。这称为积极加载。积极加载与上一个示例类似,不同的是它通过单次往返将相关产品加载到数据服务中。

下面的示例演示包含与类别相关的产品的 expand 选项。

using System;
using System.Data.Services.Client;
using NorthwindModel;

namespace TestApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            DataServiceContext ctx = new 
               DataServiceContext(
                      new Uri("https://localhost:1365/Northwind.svc"));

            // get a single category
            DataServiceQuery<Categories> categories = 
        ctx.CreateQuery<Categories>("/Categories(1)?$expand=Products");

            foreach (Categories c in categories)
            {
                //Console.WriteLine(c.CategoryName);
                richTextBox1.Text = c.CategoryName;

                foreach (Products p in c.Products)
                {
                    //Console.WriteLine("\t" + p.ProductName);
                    richTextBox1.Text = richTextBox1.Text + 
                                    "\r\n\t" + p.ProductName ;
                }
            }
        }
    }
}

更新支持

若要在数据服务中创建一个新实例,请创建 .NET 对象,然后对使用的 DataServiceContext 实例调用 AddObject,并传递此对象和目标实体集,如下面的代码段中所示。

using System;
using Microsoft.Data.WebClient;
using NorthwindModel;

namespace TestApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            DataServiceContext ctx = new
              DataServiceContext(
                  new Uri("https://localhost:1365/Northwind.svc"));

            Categories cNew = new Categories();
            cNew.CategoryName = "NewCategory1";
            cNew.Description = "Add Item Test.";

            ctx.AddObject("Categories",cNew);
            ctx.SaveChanges();
        }
    }
}

在数据服务中创建或修改实体之后,此服务将返回实体的新副本,其中包括任何可能已因触发器引发而在数据库或自动生成的键中更新的属性值。客户端库将使用这些新值自动更新 .NET 对象。

若要修改现有实体实例,请先查询该对象,再对其属性进行所需更改,然后调用 UpdateObject 方法,以向客户端库指明它需要发送该对象的更新。

Note注意

以下示例使用已通过上述代码生成工具创建的、从 DataServiceContext 派生的上下文类 NorthwindEntities。此派生的类提供特定于服务的属性,以简化针对服务的编码过程。

下面的示例演示使用 UpdateObject 方法的更新语法。

using System;
using Microsoft.Data.WebClient;
using System.Linq;
using NorthwindModel;

namespace TestApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            NorthwindEntities ctxN =
               new NorthwindEntities(
                     new Uri("https://localhost:1365/Northwind.svc"));

            var c1 = (from c in ctxN.Categories
                     where c.CategoryName == "NewCategory1"
                     select c).First();
                               
            c1.CategoryName = "UpdatedCategory";

            //ctxN.AttachTo("Categories", c1);
            ctxN.UpdateObject(c1);
            ctxN.SaveChanges();
        }
    }
}

若要删除实体实例,请对 DataServiceContext 对象调用 Delete 方法。

DataServiceContext 实例中对更改进行跟踪,但不会将更改立即发送到服务器。在完成对指定活动的所需更改后,调用 SaveChanges 以将所有更改提交给数据服务。

关联更新

更新基础结构还可以管理关联更改。可以更改 .NET 对象之间的关联,并可让客户端库在通过 HTTP 接口执行关联创建或删除操作时反映这些更改。为了说明这一点,下面的示例在 Northwind 数据库中创建了一个 Product,并将它与现有的 Category 关联。类别和产品构成了一对多关联;因此,一个指定的产品具有一个特定的类别。下面的代码演示如何使用 Add 方法向 Category 添加 Product 关联。

using System;
using Microsoft.Data.WebClient;
using System.Linq;
using NorthwindModel;

namespace TestApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            NorthwindEntities ctx = new 
             NorthwindEntities(new Uri(https://localhost:1234/Northwind.svc));
            
            var c1 = (from c in ctx.Categories
                     where c.CategoryName == "UpdatedCategory"
                     select c).First();

            Products p = new Products();
            p.ProductName = "TestProduct";
            p.Discontinued = false;
            p.QuantityPerUnit = "1";
            p.ReorderLevel = 100;
            p.UnitPrice = 1.1M;
            p.UnitsInStock = 200;
            p.UnitsOnOrder = 0;
            ctx.AddObject("Products", p);

            // Add binding between product and category
            p.Categories = c1;
            ctx.SetLink(p, "Categories", c1);

            ctx.SaveChanges();
            
            Console.ReadKey();
        }
    }

对带有双向关联的任意对象图所做的修改进行管理是一项复杂的任务;有一些高级库(例如 ADO.NET 实体框架)提供了各式各样的且一致性很高的状态管理器来处理部分具体化的图。另一方面,ADO.NET 客户端库是针对最小内存需求量设计的,它仅提供支持将数据服务操作映射到 .NET 对象所需的那些基元。

下图显示了源实体和目标实体,以及需要各种关联更新方法的实体状态。按水平轴和垂直轴查找可有效调用的方法。

目标 →

源 ↓

null

Added

Modified

Deleted

Unchanged

Added

SetLink

AddLink

SetLink

AddLink

SetLink

AddLink

SetLink

Modified

SetLink

AddLink

SetLink

AddLink

AttachLink

DeleteLink

SetLink

DeleteLink

AddLink

AttachLink

DeleteLink

SetLink

Deleted

SetLink

DeleteLink

DeleteLink

DeleteLink

Unchanged

SetLink

AddLink

AddLink

AttachLink

DeleteLink

SetLink

DeleteLink

AddLink

AttachLink

DeleteLink

SetLink

仅在源和目标都处于 Unchanged 状态时,AttachLink 才适用。AttachLink 不保持关系。不要使用 AddLink,除非希望对 SavingChanges 的调用不保存链接。

如果 ProductsCatagories 之间建立了关系,并且属性 Products.Categories 为一个集合,则使用 objCtx.AddLink(prod, "Categories", c1)

如果 Products.Cateories 只是一个引用,则使用 objCtx.SetLink(prod, "Categories", c1)

在处于 Added 状态的源实体的 SaveChanges 过程中,HTTP POST 包含指向处于 Modified 或 Unchanged 状态的其他实体的链接。

AttachLink 方法会创建一个处于 EntityStates.Unchanged 状态的链接。这是供用户创建通过上下文跟踪的链接的机制,此上下文不会发送到服务器。

客户端库中的身份验证

.NET 客户端库使用针对 HTTP 协议的 .NET Framework 支持。当与其他信息一起提供了一组凭据时,框架基础结构将通过 HTTP 自动处理身份验证方案。

默认情况下,客户端库不会向 HTTP 堆栈提供任何凭据。不过,可以在 DataServiceContext 中设置 Credentials 属性,以指向实现 ICredentials 接口的对象。有关凭据的更多信息,请参见 WebRequest.Credentials

与数据服务的异步交互

必须将 Web 应用程序设计为比在内部网络中运行的应用程序容纳更大的客户端和服务器之间的延迟。利用与服务器的异步交互,有助于在应用程序等待来自服务器的响应时保持响应的用户界面。

在此发行版中,ADO.NET 客户端库支持 DataServiceContext 类中可用的多种操作的异步操作模式,如检索和保存更改。下面的示例演示如何在代码中使用客户端库的异步 API。

using System;
using Microsoft.Data.WebClient;
using System.Linq;
using NorthwindModel;

namespace TestApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            NorthwindEntities ctx = new 
                NorthwindEntities(new Uri(https://localhost:51905/nw.svc));

            DataServiceQuery<Customers> q = ctx.Customers;

            q.BeginExecute(
                    delegate(IAsyncResult ar)
                    {
                        foreach (Customers c in q.EndExecute(ar))
                        {
                            Console.WriteLine(c.CompanyName);
                        }
                    },
                    null);


            Console.ReadKey();
        }
    }
}

另请参见

概念

HttpWebRequest GET(ADO.NET 数据服务框架)
创建 ADO.NET 数据服务

其他资源

实体数据模型