DomainContext 和操作

本主题介绍 WCF RIA Services 应用程序的客户端如何使用域上下文与应用程序中间层的域服务通信。您并不从客户端项目内直接与域服务交互,而是在客户端项目中为服务器项目中的每个域服务生成域上下文类。您在域上下文类上调用与要使用的域服务方法对应的方法。生成的域上下文类派生自 DomainContext 类。默认情况下,域上下文的名称带有 Context 后缀,而不是用于命名域服务的 Service 后缀。例如,名为 HRService 的域服务具有一个名为 HRContext 的对应域上下文。有关定义域服务方法的信息,请参见域服务

生成的域上下文包含三个构造函数方法:

  1. 用于嵌入 URI 以便通过 HTTP 与域服务通信的默认构造函数。

  2. 允许您指定替代 URI 的构造函数。

  3. 允许您提供自定义 DomainClient 实现的构造函数。此构造函数通常用于单元测试或重定向到自定义传输层。

DomainContext 类支持查询、提交和调用这三种域操作。对于每种类型的操作,都存在一个对应类,表示正在进行的异步操作。这些类分别是:LoadOperationSubmitOperationInvokeOperation

域上下文中会生成一个 EntitySet 对象,其属性会指出允许从客户端执行哪些操作(插入、更新或删除)。

查询

域上下文中的查询方法通常与域服务查询方法具有相同的名称,并带有后缀 Query。例如,域上下文中的 GetCustomersQuery 方法是从域服务中的 GetCustomers 方法生成的。域上下文查询方法返回一个可用来应用其他操作的 EntityQuery 对象。

域上下文中的所有查询都异步执行。若要执行查询,则将 EntityQuery 对象作为参数传入 Load 方法。

有关更多信息,请参见演练:检索并显示域服务中的数据

下面的代码演示如何从域服务检索客户。它将筛选电话号码以 583 开头的客户并按 LastName 的字母顺序进行排序。结果显示在 DataGrid 中。

Partial Public Class MainPage
    Inherits UserControl

    Private _customerContext As New CustomerDomainContext

    Public Sub New()
        InitializeComponent()

        Dim query As EntityQuery(Of Customer)

        query = _
            From c In Me._customerContext.GetCustomersQuery() _
            Where c.Phone.StartsWith("583") _
            Order By c.LastName

        Dim loadOp = Me._customerContext.Load(query)
        CustomerGrid.ItemsSource = loadOp.Entities
    End Sub

End Class
public partial class MainPage : UserControl
{
    private CustomerDomainContext _customerContext = new CustomerDomainContext();

    public MainPage()
    {
        InitializeComponent();
        EntityQuery<Customer> query = 
            from c in _customerContext.GetCustomersQuery()
            where c.Phone.StartsWith("583")
            orderby c.LastName
            select c;
        LoadOperation<Customer> loadOp = this._customerContext.Load(query);
        CustomerGrid.ItemsSource = loadOp.Entities;
    }
}

修改数据

当域服务包括用于更新、插入和删除实体的方法时,将不会在域上下文中生成这些方法。相反,您可以对域上下文使用 SubmitChanges 方法,随后框架会在域服务上调用适当的操作。在您调用 SubmitChanges 之前,数据源中不会进行任何更改。可以通过调用 RejectChanges 方法取消挂起的更改。

DomainContext 类还提供了 HasChangesEntityContainer 属性,让您能够评估挂起的更改。域上下文的 EntityContainer 对象将跟踪挂起的更改。挂起的更改不包括对域服务中的调用操作的调用,因为调用操作在被调用后立即执行。当您调用 SubmitChanges 时,所有挂起的更改将一起发送到域服务。

通过调用 EntitySet 对象的 Add 方法或调用 IEditableCollectionView 对象的 AddNew 方法,可以添加新的实体。当您通过调用 AddNew 方法来添加实体时,新实体在保存到数据源中之前就会在集合中呈现。

有关更多信息,请参见演练:编辑域服务中的数据

命名更新方法

对于域服务上具有公共访问修饰符且未标记 IgnoreAttribute 特性的每个命名更新方法,域上下文将为其包含一个方法。域上下文中生成的方法与域服务上的方法同名。在客户端项目中,调用方法后不会实际执行方法,直至调用 SubmitChangesEntityContainer 将对命名更新方法的所有调用作为挂起的更改进行跟踪。当您调用 SubmitChanges 时,将异步处理该方法。

同时,还会在客户端项目中,为作为参数传入命名更新方法的实体生成相同的命名更新方法。因此,可通过域上下文的实例或实体的实例来调用命名更新方法。如果您打开了生成的代码文件,则会注意到域上下文中的生成的方法只调用了实体中生成的方法。在任何一种情况下,都仍然必须调用域上下文的 SubmitChanges 方法,才能执行该命名更新方法。

以下示例演示如何对实体调用一个名为 ResetPassword 的命名更新方法。OnSubmitCompleted 是为了处理数据操作结果而实现的回调方法。

selectedCustomer.ResetPassword()
customerContext.SubmitChanges(AddressOf OnSubmitCompleted, Nothing)
selectedCustomer.ResetPassword();
customerContext.SubmitChanges(OnSubmitCompleted, null);

调用操作

对于域服务上的每个调用操作,域上下文都会包含一个方法。与域操作不同,调用操作会立即执行。不用调用 SubmitChanges 方法。调用操作将异步执行。调用操作会返回一个 InvokeOperation 对象。可以检索 Value 属性的值,获取服务操作返回的值。

在绝大多数方案中,您都应使用查询操作,而不是调用操作来加载数据。查询方法会返回单个 Entity 对象、一个 IQueryable<Entity> 对象或一个 IEnumerable<Entity> 对象。查询方法是由中间层上的 DomainService 以及客户端上的 DomainContext 支持的数据模式的必要组成部分。RIA Services 框架只会在客户端项目中为 DomainService 中的查询方法所返回的那些实体生成实体。

调用操作提供一种带外机制,用于返回非实体数据和执行具有副作用的操作。有关副作用的更多信息,请参见 HasSideEffects 属性。调用操作通常不适用于查询方法。即使调用操作返回了实体,也只在该实体由查询方法返回时,才会为客户端项目生成该实体。

下面的示例演示如何使用名为 GetLocalTemperature 的调用操作。此示例假定方法检索一个与任何实体数据都无关的值。

Dim invokeOp As InvokeOperation(Of Integer)
invokeOp = customerContext.GetLocalTemperature(selectedPostalCode, AddressOf OnInvokeCompleted, Nothing)

Private Sub OnInvokeCompleted(ByVal invOp As InvokeOperation(Of Integer))
  If (invOp.HasError) Then
    MessageBox.Show(String.Format("Method Failed: {0}", invOp.Error.Message))
    invOp.MarkErrorAsHandled()
  Else
    result = invOp.Value
  End If
End Sub
InvokeOperation<int> invokeOp = customerContext.GetLocalTemperature(selectedPostalCode, OnInvokeCompleted, null);

private void OnInvokeCompleted(InvokeOperation<int> invOp)
{
  if (invOp.HasError)
  {
    MessageBox.Show(string.Format("Method Failed: {0}", invOp.Error.Message));
    invOp.MarkErrorAsHandled();
  }
  else
  {
    result = invOp.Value;
  }
}

处理错误

当您检索或修改数据时,必须确定如何处理这些操作可能会引发的错误。当您对域上下文调用用于检索或修改数据的方法时,应包含指定错误处理步骤的参数。在加载数据时,可以指定忽略这些错误。在修改数据时,必须处理返回的异常。有关更多信息,请参见客户端上的错误处理