你的分享就是我们的动力 ---﹥

使用LINQ和ADO.NET创建Silverlight程序

时间:2011-10-24 09:55来源:www.chengxuyuans.com 点击:

在Silverlight中可以创建行业和其它以数据为中心的应用系统,但在Silverlight中处理数据不是一 件容易的事情,由于Silverlight包括许多处理数据和支持Web Service及XML的工具,但这些工具仅代表 跨过防火墙进行数据访问的最基础的部分。

常见的数据访问策略是使用Web Service和客户端LINQ共同实现的,如果你正在修改现有Web Service 端点强化你的Silverlight应用程序,那么我推荐你使用这个方法。但如果你在使用Silverlight创建一个 新的Web Service,就没有必要这么做了。

对一个典型的Web Service层而言,你在服务器上实现一个传统的数据访问策略(自定义业务对象、 LINQ to SQL、实体框架、Nhibernate等)通过Web Service暴露数据对象,Web Service仅仅是数据访问 策略下面的网关。

但为了开启完整的数据连通性,你必须要映射四个数据操作(创建、读取、更新和删除)到Web Service方法,下面是一个简单的支持Product类的service contract,注意我在本文中使用的都是C#。

例1 Product Web Service的Service Contract

[ServiceContract]
public interface ICustomerService
{
  [OperationContract]
 List GetAllProducts();
  [OperationContract]
 Product GetProduct(int productID);
 [OperationContract]
  List GetAllProductsWithCategories();
  [OperationContract]
 Product SaveProduct(Product productToSave);
  [OperationContract]
 void DeleteProduct(Product productToDelete);
}

创建一套服务来处理应用程序完整的数据模型可能是相当费时的,正如这个例子中显示的,特殊特性 的操作可能导致Web Service非常臃肿,换句话说,Web Service将有新的要求和操作要增加,甚至包括不 属于核心业务域的操作。

在例1中你看到GetAllProductsWithCategories操作默认用于检索Product和分类。即使添加排序、过 滤和分页机制到这个简单的例子你也不要感到惊讶,如果有一个简单的方法支持数据操作(如查询、排序 、过滤等)不用每次都手动构建这些机制那将是非常吸引人的,ADO.NET Data Service就正是为此而生的 。

ADO.NET Data Service

ADO.NET Data Service的目标是为数据模型提供Web访问端点,这些端点提供了数据排序、过滤、调整 和分页功能,因此开发人员就不需要在自己去编写这部分代码了,实际上,每个端点都是LINQ查询的起点 ,就从这个端点上你就可以查询你想要查找的数据。

但不要认为ADO.NET Data Service是另一个数据访问策略,实际上,ADO.NET Data Service不执行任 何直接的数据访问操作,它位于数据访问的上层,图1显示了ADO.NET Data Service和它在一个应用程序 架构中的位置。

图 1 ADO.NET Data Service层

由于ADO.NET Data Service依赖于数据访问程序完成真实的数据访问工作,你必须指定这个方法该如 何做,在ADO.NET Data Service中,每个服务(Service)必须回到开启LINQ的提供程序的后面,实际上 ,每个端点就是一个Iqueryable端点,因此ADO.NET Data Service支持任何支持Iqueryable的对象。

创建服务(Service)

当你将ADO.NET Data Service添加到你的项目中时,会创建一个新的.svc文件,代表一个服务的类, 和Web Service不同,你不需要自己亲自实现服务的操作,但要允许DataService类处理这些工作,为了运 行这些服务,有两个小任务必须执行。首先,DataService类需要一个类型参数叫做上下文对象,它是将 数据作为服务暴露的类,当你的服务从关系数据库暴露数据时,这个类是从实体框架(EntityFramework )的ObjectContext或LINQ to SQL的DataContext衍生而来的。

//使用我的NorthwindEntities上下文对象(context object)作为服务(Service)的数据源

public class Products : DataService

上下文对象没有基数类要求,实际上,你可以创建你自己的上下文对象,只要它的属性实现了 Iqueryable接口,ADO.NET Data Service将会以端点形式暴露这些属性:

public class StateContext
{
 StateList _states = new StateList();
 public IQueryable States
  {
  get { return  _states.AsQueryable(); }
  }
}

InitializeService调用中,你可以使用IdataServiceConfiguration对象指定什么类型的许可允许进 入服务,ADO.NET Data Service使用名词和动词具体指定许可,如例2所示:

例2 设置访问规则

//这个方法只被调用一次初始化服务端策略
public static void InitializeService (IDataServiceConfiguration config)
{
 //只允许我们读取和更新Products实体,不允许删除 和创建
 config.SetEntitySetAccessRule("Products",
                 EntitySetRights.AllRead |
                 EntitySetRights.WriteUpdate);
 //只允许读取Category和Suppliers实体
  config.SetEntitySetAccessRule("Categories", EntitySetRights.AllRead);
  config.SetEntitySetAccessRule("Suppliers", EntitySetRights.AllRead);
}

完成这个之后,你可以直接浏览服务了,它将会显示每个端点的原子反馈信息,为了调试ADO.NET Data Service,我建议你禁用Internet Explorer的RSS 反馈视图,或使用另一个浏览器查看服务的XML格 式。

查询和更新数据

ADO.NET Data Service将服务作为具有代表性的状态转换器(Representational State Transfer (REST))暴露,它是一个基础服务,不是基于SOAP的服务,这意味着要替换掉SOAP封包,服务响应的有效 负载只包括数据,不包括原数据(metadata),所有请求都使用HTTP动词(GET,PUT,POST等)和请求 URI描述,假定你有一个如图2所示的模型描述Products,Categories和Suppliers,ADO.NET Data Service服务将会产生三个端点,每个实体集一个,URI为了确定一个模型中的实体集,只需要使用服务的 地址和端点的名字就可以了:http://localhost/{服务名}/{端点名}或 http://localhost/Product.svc/Products。

图 2 数据模型示例

URI语法支持许多不同的特性,包括检索特殊的实体,对结果进行排序、过滤、分页和调整。

ADO.NET Data Service使用这些URI风格的查询将数据返回给服务的用户,目前支持两个序列化格式( 将来的版本很可能会进行扩展):JavaScript对象标记(JavaScript Object Notation即JSON)和基于原 子的XML(Atom-based XML)。JSON对于客户端Web代码非常有吸引力,而Atom是基于XML的格式,因此需 要借助XML解析器。

ADO.NET Data Service在查询中使用标准的HTTP访问头来确定向客户端返回什么格式,如果你从客户 端(如一个浏览器)发出一个请求可以破坏XML,如果你不通过Accept头指定一个优先选用的格式,默认 将使用Atom作为返回的格式。

查询数据只是解决方案的一部分,我们的最终目标是同时支持查询和更新,为了支持这些要求, ADO.NET Data Service映射了四个最基本的数据访问操作到基本的HTTP动词(如表1所示):

数据访问动词 HTTP动词
Create POST
Read GET
Update PUT
Delete DELETE

表 1 数据访问动词 vs HTTP动词

通过使用这些动词,ADO.NET Data Service让服务的用户可以利用所有的数据操作类型,而不用为不 同类型创建专门的端点。使用ADO.NET Data Service更新数据的唯一要求是在数据访问技术下支持 Iupdatable接口,这个接口定义了如何从ADO.NET Data Service更新和传播到数据源。

Silverlight 2.0客户端库

如果你使用ADO.NET Data Service通过URI语法和操作XML来进行查询和更新数据,你可能会得到你想 要的许多功能,但你仍然要自行构建一些管道,ADO.NET Data Service客户端库的引入就是要解决这个问 题,这个库允许你直接在Silverlight程序中进行LINQ查询,由客户端库将LINQ查询翻译成HTTP查询或更 新请求。

首先,你需要生成一些代码,这些代码读取ADO.NET Data Service服务的元数据,并为服务的实体生 成数据类。

为了生成这些代码,你需要在你的项目(Project)中添加Service Reference,你可以在项目资源管 理器(Project Explorer)中Silverlight项目上点击右键,然后选择‘添加服务引用(即Add Service Reference)’,在弹出的对话框中点击‘查找(Discover)’按钮,显示你项目中的服务(包括ADO.NET Data Service),选择ADO.NET Data Service端点,点击确定按钮。这样会创建一个新的文件,包含了每 个端点对应的data contract类和一个DataServiceContext衍生类,DataServiceContext类用作服务接入 点(暴露可查询的服务端点),这样会在你的Silverlight项目中包含这些类,并在 System.Data.Services.Client.dll(Silverlight 2 SDK的一部分)中添加一个引用。Silverlight客户 端代码和其它使用.NET的代码基于LINQ的查询非常相似,下面是示例代码:

// 创建服务类指定ADO.NET Data Service的位置
 NorthwindEntities ctx =
 new  NorthwindEntities(new Uri("Products.svc", UriKind.Relative));
//创建LINQ查询
var  qry = from p in ctx.Products
        orderby p.ProductName
         select p;

当你执行这个查询时,它会直接向目标数据发送一个Web请求,但这里的Silverlight代码和标准的 LINQ查询不同,在Silverlight中不允许同步Web请求,因此,如果要执行异步,你首先需要将查询转换成 DataServiceQuery对象,然后再调用BeginExecute启动异步执行:

// 创建一个DataServiceQuery,因为查询返回的是Products
DataServiceQuery productQuery =
  (DataServiceQuery)qry;
//指定一个callback函数执行异步查询
 productQuery.BeginExecute(new
   AsyncCallback(OnLoadComplete),
  productQuery);

当这些查询执行完后,无论操作是否成功,在AsyncCallback中指定的方法都会执行,通常你会在 AsyncCallback中包含原始查询,因此可以在callback方法中检索它,你也可以将其保存为类的一部分, 正如你在例3中看到的:

例3 将结果添加到集合中

void OnLoadComplete(IAsyncResult result)
{
  //为查询获取一个引用
   DataServiceQuery productQuery =
     (DataServiceQuery)result.AsyncState;
 
  try
  {
    //获得结果并将其添加到集合中
    List products =  productQuery.EndExecute(result).ToList();
 
  }
  catch (Exception ex)
  {
    if (HtmlPage.IsEnabled)
     {
      HtmlPage.Window.Alert("Failed to retrieve data: " + ex.ToString ());
    }
  }
 
}

如果你以前还没有处理过LINQ,理解这些模型可能就非常困难,在写本文的时候,除了在异步包中执 行LINQ(如ThreadPool和BackgroundWorker)外,还没有关于异步LINQ很好的模型,Silverlight需要所 有的请求都是异步的,因此在使用ADO.NET Data Service客户端库时需要使用这个模型。

载入相关实体

ADO.NET Data Service也允许你选择如何载入相关的实体,在前面的例子中,我是从服务器中载入 Products(产品)的,每个产品与供应商都有一个关系。使用前面的LINQ查询,我们只检索了产品,如果 我还想显示供应商和分类信息,我们可以按需载入相关信息,也可以在原始查询中明确地从服务器去检索 ,这两种技术各有各的优势,但如果你清楚地知道需要显示哪些信息,明确地载入可能更有效,如果你只 想为一些实体载入数据,使用按需检索可能会更好。

默认情况下,如果你没有明确地载入属性,关系属性(如产品供应商)就是空的,为便于按需载入, DataServiceContext类有一个BeginLoadProperty方法(遵循相同的异步模式)可以指定源实体,属性名 和callback。

public void LoadSupplierAsync(Product theProduct)
{
TheContext.BeginLoadProperty(theProduct,
"Supplier",
new AsyncCallback (SupplierLoadComplete),
null);
}

public void SupplierLoadComplete (IAsyncResult result)
{
TheContext.EndLoadProperty(result);
}

调用EndLoadProperty后,属性和相关的实体就被正确地载入,在许多情况下,你可能想在原始查询中 明确地载入它们,因为如此,LINQ提供者支持Expand扩展方法,这个方法允许你指定属性的名称路径便于 查询执行时载入它们,Expand方法在LINQ查询的from子句中使用,它告诉提供者视图载入这些相关实体, 例如,如果你使用Expand方法改变了Category 和 Supplier原始查询,在原始查询执行期间,我们的对象 将会载入这些相关实体:

var qry =
from p in TheContext.Products.Expand("Supplier").Expand("Category")
orderby p.ProductName
select p;

如果你使用ADO.NET Data Service读取数据,知道如何创建一个查询,运行它,载入你想要的相关实 体。如果你需要真实地修改数据,只需要将你的新数据绑定到你的Silverlight控制器即可。

变化管理

ADO.NET Data Service客户端库不支持对象的自动变更监视,这意味着当对象,集合和关系发生变化 时,需要开发人员告诉DataServiceContext这些变化,通知DataServiceContext对象的API相当简单,如 例4所示:

例4 DataServiceContext变更API

方法 描述
AddObject 添加一个新创建的对象
UpdateObject 标记一个已经变化的对象
DeleteObject 标记一个删除的对象
AddLink 在两个对象之间添加一个链接
UpdateLink 更新两个对象之间的链接
DeleteLink 删除两个对象之间的链接

这意味着你要监视对象的变化,并在你自己的代码中通知DataServiceContext对象,表面上 看起来这样让人很失望,因为没有实现自动化的变化管理,但这样可以让库变得更有效也更mini。

你可能会对如何监视对象的变化感到奇怪,答案就是生成的代码中,在每个生成的data contract类中 ,当类中的数据变化时partial方法被调用,如果这些方法从来没有使用过,它们本身不会造成任何资源 消耗,你可以在任何支持变化通知的data contracts上使用partial方法机制,只需要在partial方法中调 用DataServiceContract即可,不用连接DataServiceContract整个类。

幸运的是,Silverlight已经有一个接口支持变化通知了(INotifyPropertyChange),通过这个接口 可以在你的实现中将任何变化通知给感兴趣的人,例如你可以在你的data contract类(在我们的例子中 是Product类)中调用InotifyPropertyChange定义一个事件,当数据发生变化时可以激活它,下面就是具 体的示例:

public partial class Product : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
}

这样当任何属性发生变化时都可以触发一个事件,你可以通过partial方法决定什么时候触发这个事件 ,例如,当ProductName发生变化时要通知预定人,只需要调用OnProductNameChanged方法,然后触发 PropertyChanged事件,传递ProductName通知变化的属性给事件预定人,下面是代码:

partial void OnProductNameChanged()
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("ProductName"));
}
}

通过在这些可写的属性上调用这些partial方法,监视你对象的变化就很简单了,当对 象发生变化时,你可以注册PropertyChanged事件然后通知DataServiceContext对象:

//在OnLoadComplete方法中,获取结果然后将它们添加到集合中
List products = productQuery.EndExecute(result).ToList();
foreach (Product product in products)
{
//触发变化通知
product.PropertyChanged += new PropertyChangedEventHandler(product_PropertyChanged);
}

最后你可以调用product_PropertyChanged方法通知DataServiceContext对象:

void product_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Product product = (Product)sender;
TheContext.UpdateObject(product);
}

同样,在创建对象或删除对象时也需要通知DataServiceContext,如:

void addNewButton_Click(object sender, RoutedEventArgs e)
{
Product theProduct = new Product();
// ...
TheContext.AddObject(theProduct);
}
void deleteButton_Click(object sender, RoutedEventArgs e)
{
Product theProduct = (Product)theList.SelectItem;
TheContext.DeleteObject(theProduct);
theCollection.Remove(theProduct);
}

在这些代码中,你可以在你的Silverlight UI中修改这些对象,让数据绑定和变化通知 代码确保让DataServiceContext知道所有变化都会引发什么后果,但你如何对这些服务执行真实的更新呢 ?

通过服务更新

现在你的DataServiceContext对象已经知道数据的变化,但还需要一个方法通知给服务器,为了解决 这个问题,DataServiceContext类提供了一个BeginSaveChanges方法,它和本文前面描述的查询都使用了 相同的异步方法,BeginSaveChanges方法将所有变化都吸收进DataServiceContext,并将它们发送给服务 器:

TheContext.BeginSaveChanges(SaveChangesOptions.None,
new AsyncCallback(OnSaveAllComplete), null);

调用BeginSaveChanges时,有一个标 志枚举调用SaveChangesOptions,这个枚举允许你指定两个选项:是否使用批处理,是否继续,即使某些 变化保存失败。通常,我建议使用批处理,实际上,在某些父/子关系类型上批处理是必须的,因为父子 之间可能使用了引用完整性约束,这样更新才能保证父子之间的一致性。

保存完毕时,将会执行callback,有两个机制可以传播错误消息给你,首先,如果在执行保存时出 现了异常,当你在调用EndSaveChanges时,会抛出异常,因为如此,你可能想要使用try/catch来捕获灾 难性的错误;另外,EndSaveChanges返回的类型是一个DataServiceResponse对象,DataServiceResponse 有一个HasErrors属性,但在Silverlight 2 Beta 2版本库中它还不够安全:

void OnSaveAllComplete(IAsyncResult result)
{
bool succeeded = true;
try
{
DataServiceResponse response =
(DataServiceResponse)TheContext.EndSaveChanges(result);
foreach (OperationResponse opResponse in response)
{
if (opResponse.HasErrors)
{
succeeded = false;
}
}
}
catch (Exception ex)
{
succeeded = false;
}
// Alert the User
}

你可以重复使用OperationResponse对象来查看是否出现了错误,DataServiceResponse 是OperationResponse对象的一个集合,在以后的版本中,你应该可以依赖于DataServiceResponse类自身 的HasErrors属性了。

服务调式

在调试服务时,你要执行三个重要的任务:查看DataServiceContext对象中数据的状态,查看ADO.NET Data Services产生的请求,以及捕获服务器错误。

首先我们处理DataServiceContext对象中的实体状态,DataServiceContext类暴露了两个有用的集合 :Entities和Links,这些集合是只读的,由DataServiceContext进行跟踪,在调式时,不管你是将对象 标记为已变化还是未变化,在调试器中查看这些集合是非常有用的,可以帮助你确定跟踪思路是不是正确 的。

注意对你而言,查看你的Silverlight 2程序对服务器的真实请求也是很重要的,最好的方法是使用网 络代理,我个人使用的是Fiddler2,如果你对Fiddler2不熟悉,也可以使用Web traffic之类的工具来捕 获数据包,查看真正发生了什么。

对于ADO.NET Data Service而言,你可能想查看你在线上传来传去的都是什么,即Silverlight程序发 出的数据和接收到的数据,可以去我的博客 (http://wildermuth.com/2008/06/07/Debugging_ADO_NET_Data_Services_with_Fiddler)转转。

最后,.NET Framework 3.5 SP1不会将服务端错误传递给客户端了,实际上,服务器上的大部分错误 都是服务器吞下去的,调试服务端错误的最好办法是在调试菜单(Debug->Exceptions…)中使用 Exception选项,配置调试器停止一切.NET异常,如果你选择了这个选项,你可以通过服务看到抛出的异 常。

我在本文的目标是展示ADO.NET Data Service是如何在Silverlight 2和基于服务的模块之间建立起连 接的,现在你应该已经知道如何使用ADO.NET Data Service从服务器读取数据和往服务器写数据了,再也 不用自己动手设计Web Service了,正如你所看到的,Silverlight、ADO.NET Data Service和LINQ三者的 组合让你可以创建强大的基于数据驱动的Web应用程序,具有Web 2.0技术的所有有点。

转载注明地址:http://www.chengxuyuans.com/slverlight/28284.html