数据点

使用 ADO.NET 数据服务中使用 Silverlight 2

John Papa

代码下载可从 MSDN 代码库
浏览代码联机

内容

跨域 Communications
在智能关于敏感数据
入门快速 EDM
从 Silverlight 引用 ADO.NET 数据服务
使用 LINQ 检索数据
延迟加载
保存数据
提前检索数据
要被 (续)

ADO.NET 数据服务更易于创建公开数据,并允许更新通过 HTTP 使用其 RESTful (Representational 状态复制) 功能。您的 Silverlight 应用程序可以利用 ADO.NET 数据服务发送和接收使用唯一的 URI 映射到实体的数据。可以使用 LINQ 查询在 Silverlight 客户端能够通过使用 ADO.NET 数据服务 Silverlight 客户端库与服务器上实体进行交互。

ADO.NET 数据服务和 Silverlight 进行强大组合但以使其和协同工作您需要几个方面不能立即在正确理解。因此,这里我将解决某些步骤可以确保一个更成功地构建应用程序使用 ADO.NET 数据服务时的体验和 Silverlight。

跨域 Communications

ADO.NET 数据服务当前不支持跨域通信。通过标准 REST 和 SOAP 服务而不是通过 ADO.NET 数据服务支持跨域通信。(为便笺: 数据服务小组在浏览空间和将发布其进行Astoria 团队博客为就)。 这意味着 Silverlight 2 客户端无法与提供通过 ADO.NET 数据服务,如果这些服务将寄宿在不是域承载 Silverlight 客户端应用程序的不同域的服务。有关跨域策略的详细信息,请参考我2008 年 9"数据点"列. 该列中, 我将讨论文件格式和策略的工作方式。

在智能关于敏感数据

当您使用 HTTP 通信时通过 URI 发送的所有值将都是清晰可见,通过各种网络嗅探工具。一个方法以抗击这是使用 SSL 来加密所有 HTTP 通信。此外,在 URI 中发送社会保障号或任何其他专用数据,等任何敏感数据。它最好永远不会将敏感数据用作标识符。在选择任何 RESTful 通信之前一定要使用无意义的标识符,如 GUID、 数字或一个 IDENTITY 值。是例如下面的示例 URI 检索使用员工 ID 11 的雇员数据:

http://[YourDomainHere]/MyService.svc/employee(11)

数量 11 是透明的这就是很重要,不使用敏感信息的原因的 URI 中。幸运的是,在这种情况下该号 11 代表一个构造的标识符以便不公开的任何个人数据。

入门快速 EDM

可能最简单的方法上获取并使用 ADO.NET 数据运行服务是作为使用 ADO.NET 实体框架的逻辑实体模型中公开一个关系数据库中的数据。实体框架的实体数据模型 (EDM) 是完全了解如何允许读取和更新其实体访问的。EDM 此内置功能允许它使用非常少的安装程序的 ADO.NET 数据服务协同工作。

提供创建实体框架的 EDM,首先是从 Visual Studio 模板对话框中创建 ADO.NET 数据服务。这会在创建可以轻松地进行修改以公开一个 EDM 实体框架的一个模板。在类构造函数继承 DataService <t> 基类。在 T 表示在本例中是实体框架的数据源类名为实体在数据源类。在数据源类也称为对象上下文类实体框架中, 是允许 EDM 检索并保存数据的访问。由于与实体框架自动创建数据源类,创建公开 EDM 的 ADO.NET 数据服务是非常简单。在 图 1 ,代码将显示该服务类 NWDataService 继承 DataService <entities>。

图 1 创建将 DataService

public class NWDataService : DataService<Entities>
//public class NWDataService: DataService< /* TODO: put your data source 
//class name here */ >
 {
    // This method is called only once to initialize service-wide 
    //policies.
    public static void InitializeService(IDataServiceConfiguration 
        config)
    {
        //set rules to indicate which entity sets and service operations 
        //are visible, updatable, etc.
        config.SetEntitySetAccessRule("ProductSet", EntitySetRights.All);
        config.SetEntitySetAccessRule("CategorySet", 
            EntitySetRights.AllRead);
        config.SetEntitySetAccessRule("SupplierSet", 
            EntitySetRights.AllRead);
        config.SetEntitySetAccessRule("OrderSet", 
            EntitySetRights.AllRead);
        config.SetEntitySetAccessRule("OrderDetailSet", 
            EntitySetRights.All);
        config.SetEntitySetAccessRule("CustomerSet", 
            EntitySetRights.AllRead);
        /// The rest of the entity sets are not accessible.
        /// Therefore, no proxy classes are created for them either.
    }
}

下一步是要允许或拒绝读取,并在如 图 1 所示 EDM 中设置每个实体的写访问权限。完成此操作使用的所有实体集上在 * 或单独的实体上设级别指定每个实体集名称。SetEntitySetAccessRule 方法接受该规则将应用于实体组和一个或多个 EntitySetRights 枚举值的名称。请注意 图 2 中的每个实体设置 ProductSet 顺序集 OrderDetailSet、 CustomerSet、 SupplierSet,并且 CategorySet 允许所有访问。这意味着这些六个实体设置允许读取、 插入、 更新,和删除。这些服务范围内访问设置,,应用于进入系统的所有请求。没有,如 EmployeeSet 或 RegionSet,任何实体集不可访问。

图 2 System.Data.Services EntitySetRights 枚举
枚举 说明
所有 指定的实体上允许所有的读取和写入
AllRead 允许所有的读取
AllWrite 所有写入允许操作
没有访问权限允许指定的实体
ReadMultiple 允许读取多个行
ReadSingle 允许读取一行
WriteAppend 允许创建新的数据
WriteDelete 允许删除数据
WriteMerge 允许合并基于更新
WriteReplace 允许替换

EntitySetRights 枚举值指示为实体集允许的访问类型。图 2 显示了所有有效的枚举值和及其说明。权限可以被授予对全局扩展所有的实体集太。是例如下面的代码行允许对所有实体的所有读访问设置,但将不允许写访问权限:

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

权限可以组合以允许实体集,以及更多权限。 下面的代码进行设置用于读取和更新仅可访问该 ProductSet 实体:

config.SetEntitySetAccessRule("ProductSet", 
EntitySetRights.AllRead | 
EntitySetRights.WriteMerge | 
EntitySetRights.WriteReplace);

按照以下步骤,则将公开六个实体集 (如 图 1 所示) 而使它们可以通过一个 ADO.NET 数据服务。 其他实体集和权限可以自定义过。

用其他对象关系映射 (ORM) s 例如 LINQ to SQL 和 NHibernate,生成的实体模型还用于 ADO.NET 数据服务。 上下文对象必须为每个实体集被查询的实现 IQueryable。 用于创建、 更新和删除操作,上下文对象必须实现 IUpdatable 接口。 ADO.NET 数据服务有内置了解 ADO.NET 实体框架,允许它支持查询和更新通过 ADO.NET 数据服务。 很可能其他 ORMs 的未来版本将还具有对 ADO.NET 数据服务支持或至少有帮助器类以帮助 jump-start 它。 但现在,如果想要使用 ADO.NET 数据服务,实体框架之外的 ORM 您将需要实现上述的接口。 实际上,ADO.NET 数据服务不是特定于关系数据的。 通过在此段落中记下这些技术,可以配置任何数据源。

fig03.gif

图 3 生成从服务引用的类

从 Silverlight 引用 ADO.NET 数据服务

一旦 ADO.NET 数据服务已被创建并生成,Silverlight 应用程序可以引用服务,并与其进行交互。 通过右键单击服务引用节点在解决方案资源管理器中打开添加服务引用对话框窗口,可从 Silverlight 应用程序引用一个 ADO.NET 数据服务。 可以是在地址框中输入服务的 URI 也您可以单击发现按钮,然后单击的搜索按钮找到该服务。 一旦服务的元数据检索到服务和它公开的实体组将显示在列表中。 最后,服务引用并单击确定按钮。

这允许您与 ADO.NET 数据服务交互的 Silverlight 客户端创建代理类。 如果您单击解决方案资源管理器窗口中的所有文件按钮的,并完全展开 NWServiceReference 节点,您会看到 reference.cs 文件。 此文件包含生成的代理类,允许您与 ADO.NET 数据服务交互和该实体通过服务公开的生成的类。 通过查看类视图窗口,如 图 3 所示,也可能出现的类别列表。

您会注意到实体在 Web 项目中的实体框架的模型中的所有已没有列在类视图。 通过数据服务进行访问的唯一实体集需要从 Silverlight 客户端服务引用添加到 ADO.NET 数据服务时,为其生成的代理类。 类视图中显示的六个实体组公开的 ADO.NET 数据服务中使用 SetEntitySetAccessRule 方法,因此这些 Silverlight 客户端可用唯一的实体组。 在实体类类视图窗口第七个类是一个代理类,代表数据服务作为一个整体并简化 NWDataService.svc 对。

使用 LINQ 检索数据

下一步是为 System.Data.Services.client 程序集在 Silverlight 中的项目 (请参见 图 4 ) 的引用。 此程序集便于与使用 LINQ 的 ADO.NET 数据服务交互。 是例如下面的代码创建一个选择的所有使用 ADO.NET 数据服务产品的 LINQ 查询:

  _products.Clear();
  DataServiceQuery<Product> dq = (from p in _ctx.ProductSet select p)
  as
  DataServiceQuery<Product>;
  dq.BeginExecute(new AsyncCallback(FindProduct_Completed), dq);

fig04.gif

图 4 引用 ADO.NET 数据服务 ClientLibrary,从 Silverlight

LINQ 查询在此代码将转换为 ADO.NET 数据服务可以读取的 URI 中。 查询被转换为 DataServiceQuery <t>,这是在 System.Data.Services.Client 命名空间的一部分。 因此从查询中接收结果,必须指定一个有效的回调方法,异步,执行查询。 当查询在前面的代码示例执行,并返回数据时,将调用 FindProduct_Completed 方法。

FindProduct_Completed 方法 (如 图 5 所示) 接受包含从查询结果的 IAsyncResult 参数。 结果是读取调用 EndExecute 方法将产生一组产品对象。 然后检查每个产品的并且事件处理程序将分配给其 PropertyChanged 事件。 (在代码示例中我扩展使用分部类添加 INotifyPropertyChanged 实现该 Product 类)。 此步骤确保更改在 Silverlight 中, 任何产品实例时该产品将通过调用 UpdateObject 方法通知 DataServiceContext (实体, 图 5 中)。 无需此代码,DatServiceContext 将用户与产品实例所做的任何更改。 假定 _products 变量类型 ObservableCollection <product>,并且它绑定到 DataGrid 控件的 DataContext,DataGrid 控件 (请参阅中的显示产品 图 6 )。

图 5 FindProduct_Completed 方法

private void FindProduct_Completed(IAsyncResult result)
{
    DataServiceQuery<Product> query = (DataServiceQuery<Product>)result.
         AsyncState;
    try
    {
        var entities = query.EndExecute(result);
        foreach (Product item in entities)
        {
            item.PropertyChanged += ((sender, e) =>
                                         {
                                             Product entity = (Product)
                                                                sender;
                                             _ctx.UpdateObject(entity);
                                         });
            _products.Add(item);
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine("Failed to retrieve data: " + ex.ToString());
    }
}

fig06.gif

图 6 加载产品和订单明细

延迟加载

ADO.NET 数据服务客户端库在 Silverlight 中的能够加载与已在该 DataServiceContext 对象相关联的对象。 是例如该产品订单详细信息到较低的 DataGrid 绑定之前示 图 6 ,Silverlight 控件中上 DataGrid 中选择产品时, 它们必须检索。 此过程中的,第一步是在 productDataGrid 的 SelectionChanged 事件指定事件处理程序。 然后选择一种产品时, 事件处理程序 (如 图 7 所示) 使用 BeginLoadProperty 方法向 OrderDetails 对象获取所选的产品的 ADO.NET 数据服务。

图 7 索求订单明细

void productDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    Product product = productDataGrid.SelectedItem as Product;
    if (product == null) return;

    _orderDetails.Clear();

    if (product.OrderDetails == null || product.OrderDetails.Count == 0)
    {
        _ctx.BeginLoadProperty(product, "OrderDetails", FindOrderDetail_Completed, null);
    }
    else
    {
        LoadOrderDetails();
    }
}

使用 BeginLoadProperty 方法激活网络请求转获取 OrderDetails 的记录,并当它返回它将调用回调方法,FindOrderDetail_Completed。 如果任何其他状态需要从此代码传递到回调方法,可以传递 BeginLoadProperty 方法的第四个参数中。 是例如相同的事件处理程序可能被用于接收来自多个异步查询的结果。 可以将状态参数以帮助确定它收到的异步调用查询结果的回调方法传递一个值。

调用回调时, 结果读取到 DataServiceContext 对象使用其 EndLoadProperty 方法,此处所示:

_ctx.EndLoadProperty(result);
Deployment.Current.Dispatcher.BeginInvoke(() => LoadOrderDetails());

订单明细然后加载方法 LoadOrderDetails。 因为此代码运行作为异步操作完成的结果,没有能保证在 UI 线程运行此代码。

如果不在 UI 线程上运行这些代码,那么订单详细信息的新集将不会出现元素 DataGrid 中。 基本,任何用户界面操作必须运行于 UI 线程。 确保在 UI 线程上运行的操作的一种方法是使用 Dispatcher 对象的 BeginInvoke 方法。 上面的代码使用调度程序来确保在 UI 线程上由调用 LoadOrderDetails (如 图 8 所示) 加载订单明细。

图 8 加载订单详细信息

private void LoadOrderDetails()
{
    Product product = productDataGrid.SelectedItem as Product;
    if (product == null) return;

    var query = (from od in product.OrderDetails
                 orderby od.OrderID ascending
                 select od);
    foreach (OrderDetail item in query)
    {
        item.PropertyChanged += ((sender, e) =>
        {
            OrderDetail entity = (OrderDetail)sender;
            _ctx.UpdateObject(entity);
        });
        _orderDetails.Add(item);
    }
}

LoadOrderDetails 获取当前选择的产品实例,并创建将选择所有 OrderDetails 对象从所选的产品的 LINQ 查询。 这是可能的因为订单明细已加载到使用 EndLoadProperty DataServiceContext。 图 8 中使用 LINQ 查询检索到订单明细后, 每个 OrderDetail 对象实例都有其 PropertyChanged 事件处理程序分配 lambda 表达式,告诉 DataServiceContext 是否会更改任何属性值。 假定在 _orderDetails 对象是 ObservableCollection <orderdetail> 绑定到 orderDetailsDataGrid 元素,订单详细信息将出现 (如您看到中 图 6 )。

保存数据

显示该示例还允许用户保存对产品和订单明细的更改。 有两个基本步骤,以便保存所做的更改: 通知 DataServiceContext 发生更改时,并发出保存操作以异步方式。

这些分层的集的每个数据在产品和 OrderDetail 实体延伸的 Silverlight 中有一个分部类。 产品 (如 图 9 所示) 的分部类实现 INotifyPropertyChanged 接口,这需要实现 PropertyChanged 事件。 部分的产品类生成的创建服务对 ADO.NET 数据都可以使用服务创建部分的方法为每个类上的公共属性。 每个属性,获取该属性已更改时,一个属性为有关更改和一个时触发的一个方法。 例如,Product 类的 ProductName 属性都有一个 OnProductNameChanging 部分方法和 OnProductNameChanged 部分方法。 图 9 显示了自定义代码 (而不是生成类) 中的,OnProductNameChanged 部分方法触发 PropertyChanged 事件。 这是用于所有实体的属性值中跟踪所有更改密钥。 DataServiceContext 需要知道属性值的更改时并它们更改为 ; 否则它不能保存更改。

图 9 实现更改通知

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

    private void FirePropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }

    partial void OnProductIDChanged() { FirePropertyChanged("ProductID"); }
    partial void OnProductNameChanged() { FirePropertyChanged("ProductName"); }
    partial void OnDiscontinuedChanged() { FirePropertyChanged("Discontinued"); }
    partial void OnDiscontinuedDateChanged() { FirePropertyChanged("DiscontinuedDate"); }
    partial void OnQuantityPerUnitChanged() { FirePropertyChanged("QuantityPerUnit"); }
    partial void OnReorderLevelChanged() { FirePropertyChanged("ReorderLevel"); }
    partial void OnRowVersionStampChanged() { FirePropertyChanged("RowVersionStamp"); }
    partial void OnUnitPriceChanged() { FirePropertyChanged("UnitPrice"); }
    partial void OnUnitsInStockChanged() { FirePropertyChanged("UnitsInStock"); }
    partial void OnUnitsOnOrderChanged() { FirePropertyChanged("UnitsOnOrder"); }
}

之后,属性更改事件处理程序设置为每个属性,DataServiceContext 即可了解任何更改,且重工作保存数据的其余部分是相对简单。 当用户单击保存按钮时,BeginSaveChanges 方法调用 DataServiceContext,如下所示:

private void SaveButton_Clicked(object sender, RoutedEventArgs e)
{
    _ctx.BeginSaveChanges(SaveChangesOptions.Batch, 
        new AsyncCallback(Save_Complete), null);
}

此方法使服务,发送所有更改的 DataServiceContext 是了解 ADO.NET 数据的 POST 操作。 因此需要回调方法处理结果异步,发出 POST。 SaveChangesOption.batch 参数指示应将所有更改都保存在批处理模式下在事务中。 如果有的话保存操作失败它们将所有失败,并交易记录将被回滚。 此方法适用是否要保存从一个实体在单个记录或从多个关联的实体的多个记录。

提前检索数据

前面我讨论了如何执行延迟加载使用的 DataServiceContext BeginLoadProperty 方法获取现有的实体的记录,并将其附加到 DataServiceContext。 此方法非常尤其是当数据不始终需要根据中, 获取额外的数据。 延迟的加载保存检索用户可能不希望查看,除非用户要求数据正好当用户选择 productDataGrid 控件中的产品) 的数据的成本。

用于检索层次结构的数据的其他技术是要求所有事先。 是例如检索产品记录时可能有益还获取每个产品类别和供应商。 否则,类别和产品实例的供应商属性将为空。 BeginLoadProperty 方法可以将用于,但是,需要进行很多网络请求。 次获取所有数据的一个更好地技术是将方法在 LINQ 查询中,由于需要仅一个 HTTP 请求。 以下查询显示所有类别和供应商都要求所选的产品,LINQ 查询的方法。

   DataServiceQuery<Product> dq = (
    from p in _ctx.ProductSet.Expand("Categories").Expand("Suppliers")
    select p) 
    as DataServiceQuery<Product>;

以将检索其他数据,方法是成本。 仅当有必要获取预先所有数据时,才应使用此。 上述代码中所示方法获取每个产品实体的子记录。 换句话说,在类别和供应商则是一种产品的两个直接子。 请注意属性,不在实体集名称的名称传递给方法。

如果需要多个级别的层次结构扩展方法的语法可以被修改过获取这些记录。 是例如如果想要获取 OrderDetail 元素用于每种产品以及每个这些 OrderDetail 订单,语法可能如下:

DataServiceQuery<Product> dq = (
    from p in _ctx.ProductSet.Expand("OrderDetails/Orders")
    select p) 
    as DataServiceQuery<Product>;

此代码表示查询应获取订单 OrderDetails 属性每种产品以及每个实体的实体。(Product 类具有 OrderDetails 属性和 OrderDetail 类都有一个订单属性。 OrderDetail 记录被隐含的因此需要获取每个产品的订单属性时。此查询将向 Silverlight 客户端应用程序返回超过 8MB 的 XML 数据。这是大量数据,并可能负面影响较慢的连接上的性能。我建议只在需要数据时才使用该方法并即使在这的种情况下使用它可以避免检索您不需要的数据最严格的筛选器。

要被 (续)

有是相当长段进行可靠的数据驱动应用程序的 ADO.NET 数据服务和 Silverlight 的组合所公开功能。在数据点在将来期我希望重新访问该主题,并且提供更多提示。现在,请查看我的网络日志:johnpapa。netSilverlight。net 的 Web 站点,数据点的以前 installments为应用多个 Silverlight 以数据为中心程序。

将您的问题和意见发送 John 到mmdata@Microsoft.com.

John Papa (johnpapa。net) 是一个高级顾问 ASPSOFT 和棒球风扇花费在大多数时光都其家人的夏天整夜的。John、 C# MVP,Silverlight Insider,以及 INETA 演讲者已编写几部著作,包括其最新标题为的 Data-Driven Services with Silverlight 2 (O ' Reilly,2009)。他经常讲述在如组合、 DevConnections 和 VSLive 的会议。