在上一篇中我们讨论了如何在ADO.NET Entity Framework的基础上创建Data Service,并详细讲述了如何通过URL访问Data Service,并通过Query Interceptors,Change Interceptors和WebGet等来扩展和控制Data Service。创建好了服务端,那接下来就要去使用它了。如何方便并有效的使用它呢?本文将讲解在客户端调用Data Service的方法。
· DataServiceContext
DataServiceContext位于System.Data.Service.Client命名空间下,提供了Data Service在运行时的上下文对象。这个对象在客户端期间保留着与服务交互的一些状态信息。另一个重要的对象是DataServiceQuery,顾名思义这就类似于一个查询,它表示一个特定的以URI为基础的查询对象。但通常DataServiceContext.Execute方法更为常用。下面的示例创建了一个DataServiceContext对象并传递了一个查询所有Employees的URI参数:
using System.Data.Services.Client; … DataServiceContext context = new DataServiceContext(new Uri(ServiceUri)); IEnumerable<Employees> employees = context.Execute<Employees>( new Uri("Employees?$orderby=FirstName", UriKind.Relative)); return employees.ToList(); |
我们可以利用URI和DataServiceContext对象来创建更多的查询,但是这里的限制是你无法使用Service已经定义好的对象模型。例如上边的Employees,你需要在客户端也定义相同的类型。当然,这不是我们最好的选择。通过类似于WCF/Web Service的代理引用模式,会更方便一些。
· 通过Add Service Reference自动生成代理类
添加Service引用可以很容易的自动生成客户端代理类并包括了所有类型的定义,这样所有的调用通过代理类便可以轻松完成。
另一个方法与添加服务引用相似,都是生成代理类,那就是datasvcutil.exe命令行。它接收一个指向Service本身的URL地址以及生成代理类的名称。(这里你也可以通过/language:VB/C#来指定生成代理类的语言)。
datasvcutil.exe /out:NorthwindProxy.cs /uri:”http://localhost:5000/NorthwindService.svc” |
生成了客户端代理类,那就意味着所有的对数据库的操作现在变成了通过Data Service来操作Entity Framework的对象了,这个时候就可以利用LINQ to Object来简化我们的操作了。代理类里包含一个Entity Framework的主要类----在我们的示例中就是NorthwindEntities,它就是我们操作Data Service的上下文对象。通过给NorthwindEntities传一个Service的URI地址,它就帮助我们完成与Data Service的交互。以下代码片段完成了和上边示例中用DataServiceClient一样的功能:
NorthwindEntities context = new NorthwindEntities(new Uri(ServiceUri)); var employees = from employee in context.Employees orderby employee.FirstName select employee; return employees.ToList(); |
可以很清楚的看到,这里使用了LINQ来将Employees中的记录排序。这里,除了第一行意外,完全和Entity Framework一样,不用感到奇怪,因为Data Service本身就是建立在Entity Framework之上的,它只是暴露给了我们接口,所有本质上的事情还是通过EF来完成的。
· Delay Load
ADO.NET Data Service URI提供了Expand参数,用来提供类似于LINQ中的延迟加载,其实他们都是通过关系来指定的,只不过Data Service里的Expand表现的不是那么明显,是通过它本身包含的某个集合的属性名称来表示指定延迟加载的对象。下边的实例通过使用URI中的?expand=Orders来同时加载一个Employees对象的所属Orders集合。
public List<Employees> GetEmployeeWithOrders(int employeeID) { DataServiceContext ctx = new DataServiceContext(new Uri(ServiceUri)); ctx.MergeOption = MergeOption.AppendOnly; IEnumerable<Employees> employees = ctx.Execute<Employees>(new Uri("Employees(" + employeeID.ToString() +")?$expand=Orders", UriKind.Relative)); return employees.ToList(); } |
这是使用DataServiceContext对象来完成的,我们同样可以使用客户端代理来制定LoadProperty来完成,但看起来这样的方式并不是那么优雅。
DataServiceContext ctx = new DataServiceContext(new Uri(ServiceUri)); ctx.MergeOption = MergeOption.AppendOnly; // get a single category IEnumerable<Employees> employees = ctx.Execute<Employees>(new Uri("Employees(" + employeeID.ToString() +")", UriKind.Relative)); foreach (Employees e in employees) { ctx.LoadProperty(e, "Orders"); Console.WriteLine(e.Orders[0].OrderID.ToString()); } |
在不指定ctx.LoadProperty之前,Employee对象没有加载Orders集合。而在对每个Employees对象调用ctx.LoadProperty方法之后,Orders集合被加载到Employees对象。
另一种方法是使用LINQ to Object里的Expand方法,看起来它更简单些:)
var a = from e in context.Employees.Expand("Orders")
· Data Manipulation
Data Service在暴露了数据访问的接口并提供了数据操作的接口。目前为止,已经看到如何使用DataServiceContext对象或客户端代理类来获取列表了,对所有数据的访问,无外乎增删改查,接下来看看如何来实现对应的几个方法。
添加对象:Data Service提供了统一的Context.AddObject方法来接受INSERT的操作请求--只需要将对象实例传递给AddObject方法,并随后调用SaveChanges()提交即可。
public void NewEmployee(DataServiceClient.NorthwindProxy.Employees employee) { NorthwindEntities context = new NorthwindEntities(new Uri(ServiceUri)); context.AddObject("Employees", employee); context.SaveChanges(); } |
更新和删除对象:Data Service在更新或删除某个对象时,必须保证其本身是被Data Service的上下文对象所跟踪的,这样保证了在服务端删除时它被删除的是一个你所确认的对象并有效的保护相应的访问请求。这个操作时通过AttachTo来实现的。
public void UpdateEmployee(Employees employee) { NorthwindEntities context = new NorthwindEntities(new Uri(ServiceUri)); context.AttachTo("Employees", employee); context.UpdateObject(employee); context.SaveChanges(); } |
public void DeleteEmployee(int employeeID) { NorthwindEntities context = new NorthwindEntities(new Uri(ServiceUri)); Employees employee = GetEmployee(employeeID); context.AttachTo("Employees", employee); context.DeleteObject(employee); context.SaveChanges(); } |
· Batching
我们可以认为Batching是加入了事务的概念,也可以认为这是为了节约HTTP的传输而将多次请求放在一起提交给服务器完成。它的含义是,在调用SaveChanges方法时加入SaveChanangesOptions.Batch参数用来指明服务器需要将上下文对象中某个会话空间内的几次请求合并成一个请求包发送给服务端。在服务端接受到这样的请求后被认为是原子性(atomic)的,要么所有操作完成,要么所有操作都失败。
· 异步调用
异步调用对于Ajax方式的访问来说是一种习惯,Silverlight中的所有请求都是按异步方式来设计的。对于Data Service的异步访问需要传递一个DataServiceQuery对象。声明一个DataServiceQuery实例的方式看其来很奇怪,但这也是它的高明之处,直接将一个LINQ的对象强制转换成DataServiceQuery即可:
int employeeId = (int)ViewState["EmployeeId"]; NorthwindEntities context = new NorthwindEntities(new Uri("http://localhost:5000/NorthwindService.svc")); var query = from order in context.Orders where order.Employees.EmployeeID == employeeId select order; DataServiceQuery<Orders> orderQuery = (DataServiceQuery<Orders>)query; orderQuery.BeginExecute(new AsyncCallback(OnLoadComplete), orderQuery); |
定义Callback函数来做数据消费:
private void OnLoadComplete(IAsyncResult ar) { DataServiceQuery<Orders> query = (DataServiceQuery<Orders>)ar.AsyncState; List<Orders> orders = query.EndExecute(ar).ToList(); this.gridList.DataSource = orders; this.gridList.DataBind(); } |
CodePlex上提供了针对ADO.NET Data Service的AJAX访问类库,这可以让我们用Javascript来访问Data Srevice。下次再聊:)