使用liNQ和ADO.NET创建Silverlight程序@H_419_9@
@H_419_9@
注:本文转自 51cto.com@H_419_9@
@H_419_9@
@H_419_9@
在Silverlight中可以创建行业和其它以数据为中心的应用系统,但在Silverlight中处理数据不是一件容易的事情,由于Silverlight包括许多处理数据和支持Web Service及XML的工具,但这些工具仅代表跨过防火墙进行数据访问的最基础的部分。 @H_419_9@
常见的数据访问策略是使用Web Service和客户端liNQ共同实现的,如果你正在修改现有Web Service端点强化你的Silverlight应用程序,那么我推荐你使用这个方法。但如果你在使用Silverlight创建一个新的Web Service,就没有必要这么做了。@H_419_9@
对一个典型的Web Service层而言,你在服务器上实现一个传统的数据访问策略(自定义业务对象、liNQ to sql、实体框架、Nhibernate等)通过Web Service暴露数据对象,Web Service仅仅是数据访问策略下面的网关。@H_419_9@
但为了开启完整的数据连通性,你必须要映射四个数据 *** 作(创建、读取、更新和删除)到Web Service方法,下面是一个简单的支持Product类的service contract,注意我在本文中使用的都是C#。@H_419_9@
例1 Product Web Service的Service Contract@H_419_9@
[ServiceContract]@H_419_9@ public interface ICustomerService@H_419_9@ {@H_419_9@ [OperationContract]@H_419_9@ List GetAllProducts();@H_419_9@ [OperationContract]@H_419_9@ Product GetProduct(int productID);@H_419_9@ [OperationContract]@H_419_9@ List GetAllProductsWithCategorIEs();@H_419_9@ [OperationContract]@H_419_9@ Product SaveProduct(Product productToSave);@H_419_9@ [OperationContract]@H_419_9@ voID DeleteProduct(Product productToDelete);@H_419_9@ }@H_419_9@ |
创建一套服务来处理应用程序完整的数据模型可能是相当费时的,正如这个例子中显示的,特殊特性的 *** 作可能导致Web Service非常臃肿,换句话说,Web Service将有新的要求和 *** 作要增加,甚至包括不属于核心业务域的 *** 作。@H_419_9@
在例1中你看到GetAllProductsWithCategorIEs *** 作默认用于检索Product和分类。即使添加排序、过滤和分页机制到这个简单的例子你也不要感到惊讶,如果有一个简单的方法支持数据 *** 作(如查询、排序、过滤等)不用每次都手动构建这些机制那将是非常吸引人的,ADO.NET Data Service就正是为此而生的。@H_419_9@
ADO.NET Data Service@H_419_9@
ADO.NET Data Service的目标是为数据模型提供Web访问端点,这些端点提供了数据排序、过滤、调整和分页功能,因此开发人员就不需要在自己去编写这部分代码了,实际上,每个端点都是liNQ查询的起点,就从这个端点上你就可以查询你想要查找的数据。@H_419_9@
但不要认为ADO.NET Data Service是另一个数据访问策略,实际上,ADO.NET Data Service不执行任何直接的数据访问 *** 作,它位于数据访问的上层,图1显示了ADO.NET Data Service和它在一个应用程序架构中的位置。@H_419_9@
@H_419_9@
图 1 ADO.NET Data Service层@H_419_9@
由于ADO.NET Data Service依赖于数据访问程序完成真实的数据访问工作,你必须指定这个方法该如何做,在ADO.NET Data Service中,每个服务(Service)必须回到开启liNQ的提供程序的后面,实际上,每个端点就是一个Iqueryable端点,因此ADO.NET Data Service支持任何支持Iqueryable的对象。@H_419_9@
创建服务(Service)@H_419_9@
当你将ADO.NET Data Service添加到你的项目中时,会创建一个新的.svc文件,代表一个服务的类,和Web Service不同,你不需要自己亲自实现服务的 *** 作,但要允许DataService类处理这些工作,为了运行这些服务,有两个小任务必须执行。首先,DataService类需要一个类型参数叫做上下文对象,它是将数据作为服务暴露的类,当你的服务从关系数据库暴露数据时,这个类是从实体框架(EntityFramework)的ObjectContext或liNQ to sql的DataContext衍生而来的。@H_419_9@
//使用我的northwindEntitIEs上下文对象(context object)作为服务(Service)的数据源@H_419_9@
public class Products : DataService@H_419_9@
上下文对象没有基数类要求,实际上,你可以创建你自己的上下文对象,只要它的属性实现了Iqueryable接口,ADO.NET Data Service将会以端点形式暴露这些属性:@H_419_9@
public class StateContext@H_419_9@ {@H_419_9@ StateList _states = new StateList();@H_419_9@ public Iqueryable States @H_419_9@ {@H_419_9@ get { return _states.Asqueryable(); } @H_419_9@ }@H_419_9@ }@H_419_9@ |
InitializeService调用中,你可以使用IDataServiceConfiguration对象指定什么类型的许可允许进入服务,ADO.NET Data Service使用名词和动词具体指定许可,如例2所示:@H_419_9@
例2 设置访问规则@H_419_9@
//这个方法只被调用一次初始化服务端策略@H_419_9@ public static voID InitializeService(IDataServiceConfiguration config)@H_419_9@ {@H_419_9@ //只允许我们读取和更新Products实体,不允许删除和创建@H_419_9@ config.SetEntitySetAccessRule("Products",@H_419_9@ EntitySetRights.AllRead | @H_419_9@ EntitySetRights.WriteUpdate);@H_419_9@ //只允许读取category和suppliers实体@H_419_9@ config.SetEntitySetAccessRule("CategorIEs",EntitySetRights.AllRead);@H_419_9@ config.SetEntitySetAccessRule("suppliers",EntitySetRights.AllRead);@H_419_9@ }@H_419_9@ |
完成这个之后,你可以直接浏览服务了,它将会显示每个端点的原子反馈信息,为了调试ADO.NET Data Service,我建议你禁用Internet Explorer的RSS 反馈视图,或使用另一个浏览器查看服务的XML格式。 @H_419_9@
查询和更新数据 @H_419_9@
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。@H_419_9@
@H_419_9@
图 2 数据模型示例@H_419_9@
URI语法支持许多不同的特性,包括检索特殊的实体,对结果进行排序、过滤、分页和调整。@H_419_9@
ADO.NET Data Service使用这些URI风格的查询将数据返回给服务的用户,目前支持两个序列化格式(将来的版本很可能会进行扩展):JavaScript对象标记(JavaScript Object Notation即JsON)和基于原子的XML(Atom-based XML)。JsON对于客户端Web代码非常有吸引力,而Atom是基于XML的格式,因此需要借助XML解析器。@H_419_9@
ADO.NET Data Service在查询中使用标准的http访问头来确定向客户端返回什么格式,如果你从客户端(如一个浏览器)发出一个请求可以破坏XML,如果你不通过Accept头指定一个优先选用的格式,默认将使用Atom作为返回的格式。@H_419_9@
查询数据只是解决方案的一部分,我们的最终目标是同时支持查询和更新,为了支持这些要求,ADO.NET Data Service映射了四个最基本的数据访问 *** 作到基本的http动词(如表1所示):@H_419_9@
数据访问动词@H_419_9@ | http动词@H_419_9@ |
Create@H_419_9@ | POST@H_419_9@ |
Read@H_419_9@ | GET@H_419_9@ |
Update@H_419_9@ | PUT@H_419_9@ |
Delete@H_419_9@ | DELETE@H_419_9@ |
表 1 数据访问动词 vs http动词@H_419_9@
通过使用这些动词,ADO.NET Data Service让服务的用户可以利用所有的数据 *** 作类型,而不用为不同类型创建专门的端点。使用ADO.NET Data Service更新数据的唯一要求是在数据访问技术下支持Iupdatable接口,这个接口定义了如何从ADO.NET Data Service更新和传播到数据源。@H_419_9@
Silverlight 2.0客户端库@H_419_9@
如果你使用ADO.NET Data Service通过URI语法和 *** 作XML来进行查询和更新数据,你可能会得到你想要的许多功能,但你仍然要自行构建一些管道,ADO.NET Data Service客户端库的引入就是要解决这个问题,这个库允许你直接在Silverlight程序中进行liNQ查询,由客户端库将liNQ查询翻译成http查询或更新请求。@H_419_9@
首先,你需要生成一些代码,这些代码读取ADO.NET Data Service服务的元数据,并为服务的实体生成数据类。@H_419_9@
为了生成这些代码,你需要在你的项目(Project)中添加Service Reference,你可以在项目资源管理器(Project Explorer)中Silverlight项目上点击右键,然后选择‘添加服务引用(即Add Service Reference)’,在d出的对话框中点击‘查找(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的查询非常相似,下面是示例代码:@H_419_9@
// 创建服务类指定ADO.NET Data Service的位置@H_419_9@ northwindEntitIEs ctx = @H_419_9@ new northwindEntitIEs(new Uri("Products.svc",UriKind.relative));@H_419_9@ //创建liNQ查询@H_419_9@ var qry = from p in ctx.Products@H_419_9@ orderby p.Productname@H_419_9@ select p;@H_419_9@ |
当你执行这个查询时,它会直接向目标数据发送一个Web请求,但这里的Silverlight代码和标准的liNQ查询不同,在Silverlight中不允许同步Web请求,因此,如果要执行异步,你首先需要将查询转换成DataServicequery对象,然后再调用BeginExecute启动异步执行:@H_419_9@
// 创建一个DataServicequery,因为查询返回的是Products@H_419_9@ DataServicequery productquery =@H_419_9@ (DataServicequery)qry;@H_419_9@ //指定一个callback函数执行异步查询@H_419_9@ productquery.BeginExecute(new @H_419_9@ AsyncCallback(OnLoadComplete),@H_419_9@ productquery);@H_419_9@ |
当这些查询执行完后,无论 *** 作是否成功,在AsyncCallback中指定的方法都会执行,通常你会在AsyncCallback中包含原始查询,因此可以在callback方法中检索它,你也可以将其保存为类的一部分,正如你在例3中看到的:@H_419_9@
例3 将结果添加到集合中@H_419_9@
voID OnLoadComplete(IAsyncResult result)@H_419_9@
{@H_419_9@
//为查询获取一个引用@H_419_9@
DataServicequery productquery =@H_419_9@
(DataServicequery)result.AsyncState;@H_419_9@
@H_419_9@
try@H_419_9@
{@H_419_9@
//获得结果并将其添加到集合中@H_419_9@
List products = productquery.EndExecute(result).ToList();@H_419_9@
@H_419_9@
}@H_419_9@
catch (Exception ex)@H_419_9@
{@H_419_9@
if (HTMLPage.IsEnabled)@H_419_9@
{@H_419_9@
HTMLPage.Window.Alert("Failed to retrIEve data: " + ex.ToString());@H_419_9@
}@H_419_9@
}@H_419_9@
@H_419_9@
}@H_419_9@
如果你以前还没有处理过liNQ,理解这些模型可能就非常困难,在写本文的时候,除了在异步包中执行liNQ(如ThreadPool和BackgrounDWorker)外,还没有关于异步liNQ很好的模型,Silverlight需要所有的请求都是异步的,因此在使用ADO.NET Data Service客户端库时需要使用这个模型。@H_419_9@
载入相关实体@H_419_9@
ADO.NET Data Service也允许你选择如何载入相关的实体,在前面的例子中,我是从服务器中载入Products(产品)的,每个产品与供应商都有一个关系。使用前面的liNQ查询,我们只检索了产品,如果我还想显示供应商和分类信息,我们可以按需载入相关信息,也可以在原始查询中明确地从服务器去检索,这两种技术各有各的优势,但如果你清楚地知道需要显示哪些信息,明确地载入可能更有效,如果你只想为一些实体载入数据,使用按需检索可能会更好。@H_419_9@
默认情况下,如果你没有明确地载入属性,关系属性(如产品供应商)就是空的,为便于按需载入,DataServiceContext类有一个BeginLoadProperty方法(遵循相同的异步模式)可以指定源实体,属性名和callback。@H_419_9@
public voID LoadsupplierAsync(Product theProduct) |
调用EndLoadProperty后,属性和相关的实体就被正确地载入,在许多情况下,你可能想在原始查询中明确地载入它们,因为如此,liNQ提供者支持Expand扩展方法,这个方法允许你指定属性的名称路径便于查询执行时载入它们,Expand方法在liNQ查询的from子句中使用,它告诉提供者视图载入这些相关实体,例如,如果你使用Expand方法改变了category 和 supplier原始查询,在原始查询执行期间,我们的对象将会载入这些相关实体:@H_419_9@
var qry = |
如果你使用ADO.NET Data Service读取数据,知道如何创建一个查询,运行它,载入你想要的相关实体。如果你需要真实地修改数据,只需要将你的新数据绑定到你的Silverlight控制器即可。 @H_419_9@
变化管理 @H_419_9@
ADO.NET Data Service客户端库不支持对象的自动变更监视,这意味着当对象,集合和关系发生变化时,需要开发人员告诉DataServiceContext这些变化,通知DataServiceContext对象的API相当简单,如例4所示:@H_419_9@
例4 DataServiceContext变更API@H_419_9@
方法@H_419_9@ | 描述@H_419_9@ |
Addobject@H_419_9@ | 添加一个新创建的对象@H_419_9@ |
UpdateObject@H_419_9@ | 标记一个已经变化的对象@H_419_9@ |
DeleteObject@H_419_9@ | 标记一个删除的对象@H_419_9@ |
Addlink@H_419_9@ | 在两个对象之间添加一个链接@H_419_9@ |
Updatelink@H_419_9@ | 更新两个对象之间的链接@H_419_9@ |
Deletelink@H_419_9@ | 删除两个对象之间的链接@H_419_9@ |
这意味着你要监视对象的变化,并在你自己的代码中通知DataServiceContext对象,表面上看起来这样让人很失望,因为没有实现自动化的变化管理,但这样可以让库变得更有效也更mini。@H_419_9@
你可能会对如何监视对象的变化感到奇怪,答案就是生成的代码中,在每个生成的data contract类中,当类中的数据变化时partial方法被调用,如果这些方法从来没有使用过,它们本身不会造成任何资源消耗,你可以在任何支持变化通知的data contracts上使用partial方法机制,只需要在partial方法中调用DataServiceContract即可,不用连接DataServiceContract整个类。@H_419_9@
幸运的是,Silverlight已经有一个接口支持变化通知了(INotifyPropertyChange),通过这个接口可以在你的实现中将任何变化通知给感兴趣的人,例如你可以在你的data contract类(在我们的例子中是Product类)中调用InotifyPropertyChange定义一个事件,当数据发生变化时可以激活它,下面就是具体的示例:@H_419_9@
public partial class Product : INotifyPropertyChanged@H_419_9@ {@H_419_9@ public event PropertyChangedEventHandler PropertyChanged;@H_419_9@ }@H_419_9@ |
这样当任何属性发生变化时都可以触发一个事件,你可以通过partial方法决定什么时候触发这个事件,例如,当Productname发生变化时要通知预定人,只需要调用OnProductnameChanged方法,然后触发PropertyChanged事件,传递Productname通知变化的属性给事件预定人,下面是代码:@H_419_9@
partial voID OnProductnameChanged() |
通过在这些可写的属性上调用这些partial方法,监视你对象的变化就很简单了,当对象发生变化时,你可以注册PropertyChanged事件然后通知DataServiceContext对象:@H_419_9@
//在OnLoadComplete方法中,获取结果然后将它们添加到集合中 |
@H_419_9@
最后你可以调用product_PropertyChanged方法通知DataServiceContext对象:@H_419_9@
@H_419_9@
voID product_PropertyChanged(object sender,PropertyChangedEventArgs e)@H_419_9@ {@H_419_9@ Product product = (Product)sender;@H_419_9@ TheContext.UpdateObject(product);@H_419_9@ }@H_419_9@ |
同样,在创建对象或删除对象时也需要通知DataServiceContext,如:@H_419_9@
voID addNewbutton_Click(object sender,RoutedEventArgs e)@H_419_9@ {@H_419_9@ Product theProduct = new Product();@H_419_9@ // ...@H_419_9@ TheContext.Addobject(theProduct);@H_419_9@ }@H_419_9@ @H_419_9@ voID deletebutton_Click(object sender,RoutedEventArgs e)@H_419_9@ {@H_419_9@ Product theProduct = (Product)theList.SelectItem;@H_419_9@ TheContext.DeleteObject(theProduct);@H_419_9@ theCollection.Remove(theProduct);@H_419_9@ }@H_419_9@ |
在这些代码中,你可以在你的Silverlight UI中修改这些对象,让数据绑定和变化通知代码确保让DataServiceContext知道所有变化都会引发什么后果,但你如何对这些服务执行真实的更新呢?@H_419_9@
通过服务更新@H_419_9@
现在你的DataServiceContext对象已经知道数据的变化,但还需要一个方法通知给服务器,为了解决这个问题,DataServiceContext类提供了一个BeginSaveChanges方法,它和本文前面描述的查询都使用了相同的异步方法,BeginSaveChanges方法将所有变化都吸收进DataServiceContext,并将它们发送给服务器:@H_419_9@
TheContext.BeginSaveChanges(SaveChangesOptions.None,@H_419_9@ new AsyncCallback(OnSaveAllComplete),@H_419_9@ null);@H_419_9@ |
调用BeginSaveChanges时,有一个标志枚举调用SaveChangesOptions,这个枚举允许你指定两个选项:是否使用批处理,是否继续,即使某些变化保存失败。通常,我建议使用批处理,实际上,在某些父/子关系类型上批处理是必须的,因为父子之间可能使用了引用完整性约束,这样更新才能保证父子之间的一致性。@H_419_9@
保存完毕时,将会执行callback,有两个机制可以传播错误消息给你,首先,如果在执行保存时出现了异常,当你在调用EndSaveChanges时,会抛出异常,因为如此,你可能想要使用try/catch来捕获灾难性的错误;另外,EndSaveChanges返回的类型是一个DataServiceResponse对象,DataServiceResponse有一个HasErrors属性,但在Silverlight 2 Beta 2版本库中它还不够安全:@H_419_9@
voID OnSaveAllComplete(IAsyncResult result)@H_419_9@
{@H_419_9@
bool succeeded = true;@H_419_9@
try@H_419_9@
{@H_419_9@
DataServiceResponse response = @H_419_9@
(DataServiceResponse)TheContext.EndSaveChanges(result);@H_419_9@
@H_419_9@
foreach (OperationResponse opResponse in response)@H_419_9@
{@H_419_9@
if (opResponse.HasErrors)@H_419_9@
{@H_419_9@
succeeded = false;@H_419_9@
}@H_419_9@
}@H_419_9@
@H_419_9@
}@H_419_9@
catch (Exception ex)@H_419_9@
{@H_419_9@
succeeded = false;@H_419_9@
}@H_419_9@
@H_419_9@
// Alert the User@H_419_9@
}@H_419_9@
你可以重复使用OperationResponse对象来查看是否出现了错误,DataServiceResponse是OperationResponse对象的一个集合,在以后的版本中,你应该可以依赖于DataServiceResponse类自身的HasErrors属性了。@H_419_9@
服务调式@H_419_9@
在调试服务时,你要执行三个重要的任务:查看DataServiceContext对象中数据的状态,查看ADO.NET Data Services产生的请求,以及捕获服务器错误。@H_419_9@
首先我们处理DataServiceContext对象中的实体状态,DataServiceContext类暴露了两个有用的集合:EntitIEs和links,这些集合是只读的,由DataServiceContext进行跟踪,在调式时,不管你是将对象标记为已变化还是未变化,在调试器中查看这些集合是非常有用的,可以帮助你确定跟踪思路是不是正确的。@H_419_9@
注意对你而言,查看你的Silverlight 2程序对服务器的真实请求也是很重要的,最好的方法是使用网络代理,我个人使用的是fiddler2,如果你对fiddler2不熟悉,也可以使用Web traffic之类的工具来捕获数据包,查看真正发生了什么。@H_419_9@
对于ADO.NET Data Service而言,你可能想查看你在线上传来传去的都是什么,即Silverlight程序发出的数据和接收到的数据,可以去我的博客(http://wildermuth.com/2008/06/07/DeBUGging_ADO_NET_Data_Services_with_fiddler)转转。@H_419_9@
最后,.NET Framework 3.5 SP1不会将服务端错误传递给客户端了,实际上,服务器上的大部分错误都是服务器吞下去的,调试服务端错误的最好办法是在调试菜单(DeBUG->Exceptions…)中使用Exception选项,配置调试器停止一切.NET异常,如果你选择了这个选项,你可以通过服务看到抛出的异常。@H_419_9@
我在本文的目标是展示ADO.NET Data Service是如何在Silverlight 2和基于服务的模块之间建立起连接的,现在你应该已经知道如何使用ADO.NET Data Service从服务器读取数据和往服务器写数据了,再也不用自己动手设计Web Service了,正如你所看到的,Silverlight、ADO.NET Data Service和liNQ三者的组合让你可以创建强大的基于数据驱动的Web应用程序,具有Web 2.0技术的所有有点。@H_419_9@ 总结
以上是内存溢出为你收集整理的使用LINQ和ADO.NET创建Silverlight程序全部内容,希望文章能够帮你解决使用LINQ和ADO.NET创建Silverlight程序所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)