演练:在多个域服务间共享实体

在 WCF RIA Services 应用程序中,您可能希望显示来自各种数据源的数据或向多个域服务公开一个实体。例如,电子商务网站可能需要将其订单处理系统中的数据与第三方域服务中的产品集成。RIA Services 通过支持来自不同 DomainContext 类型的实体之间的引用来启用此方案。有关此功能的特性和限制的更多信息,请参见共享实体主题。

在本演练中,将为您演示用于在不同的域上下文实例中定义实体之间的关联的两种不同方法:

  • 在第一部分中,通过在服务器项目中添加代码来定义关联。

  • 在第二部分中,通过在服务器项目中使用代码来定义关联。

这两种定义关联的方法都会在客户端上使用同一代码来检索和显示数据。

为了便于在本演练中进行说明,您将创建两个实体模型和两个域服务以便公开 AdventureWorksLT 示例数据库中的数据。通常,您不会创建两个实体模型和两个域服务来公开单个源中的数据。我们在此处使用此方法来简化示例,同时了解稍后可在采用多个数据源的更复杂方案中使用的技术。有关显示单个数据源中的相关数据的其他示例,请参见演练:在 Silverlight 业务应用程序中显示相关数据

必备条件

除 WCF RIA Services 和 WCF RIA Services 工具包之外,本演练和 RIA Services 文档中提供的其他演练还要求正确安装和配置 Visual Studio 2010 和 Silverlight Developer 运行时及 SDK 等必备程序。它们还要求安装和配置具有高级服务的 SQL Server 2008 R2 Express,并安装 AdventureWorks OLTP 和 LT 数据库。

WCF RIA Services 的必备条件节点中的主题提供有关如何满足这些前提条件的详细说明。在继续本演练之前,请按照此处提供的说明执行操作,以确保您在执行本 RIA Services 演练时尽可能少地遇到问题。

创建解决方案、数据模型和域服务

设置 RIA Services 解决方案

  1. 在 Visual Studio 2010 中,通过依次选择**“文件”“新建”“项目”**来创建新的 RIA Services 项目。

    此时将出现**“新建项目”**对话框。

  2. 从**“Silverlight”模板中选择“Silverlight 应用程序”**模板,并将新项目命名为 SharedEntityExample

  3. 单击**“确定”**。

    此时将出现**“新建 Silverlight 应用程序”**对话框。

  4. 选中窗口底部的**“启用 WCF RIA Services”**复选框。

  5. 单击**“确定”**创建解决方案。

创建两个实体数据模型

  1. 在**“解决方案资源管理器”中,右击服务器项目 (SharedEntityExample.Web),选择“添加”,然后选择“新建项”**。

    此时将出现**“添加新项”**对话框。

  2. 从左侧的**“已安装的模板”列表中选择“数据”,并选择“ADO.NET 实体数据模型”**模板。

  3. 将新文件命名为 SalesModel.edmx,并单击**“添加”**。

    此时将出现**“实体数据模型向导”**。

  4. 在**“选择模型内容”屏幕上,选择“从数据库生成”,然后单击“下一步”**。

  5. 在**“选择您的数据连接”**屏幕上,创建与 AdventureWorksLT 数据库的数据连接。

  6. 如果 AdventureWorksLT 数据库未出现在下拉列表中,请单击**“新建连接”,选择正确的“服务器名称”,然后从窗口下部的“连接到数据库”框的下拉菜单中选择“AdventureWorksLT”数据库。选择“测试连接”按钮以确保数据库是可访问的,并单击“确定”**。

  7. 请确保在返回到**“实体数据模型向导”时选中“将 Web.Config 中的实体连接设置另存为**复选框,并将实体连接设置的值更改为 Sales_DataEntities

  8. 单击**“下一步”**。

  9. 在**“选择数据库对象”屏幕上,选择“SalesOrderHeader”**表。

  10. 单击**“完成”**。

    这将为该表创建实体模型。

  11. 重复本节中前面的步骤,为 AdventureWorksLT 数据库创建另一个实体数据模型,将该模型命名为 CustomerModel.edmx,将 Web.config 中的实体连接设置的值更改为 Customer_DataEntities,然后选择**“Customer (SalesLT)”**表。

  12. 生成解决方案。

  13. 打开 Sales 实体模型的代码文件,您会发现,SalesOrderHeader 类具有一个 CustomerID 属性。您将使用此属性将 SalesOrderHeaderCustomer 关联。

创建域服务

  1. 右击 SharedEntityExample.Web 服务器项目,选择**“添加”“新建项”**。

  2. 在类别列表中,选择**“Web”,然后选择“域服务类”**模板。

  3. 将类命名为 SalesDomainService.cs(或 SalesDomainService.vb)。

  4. 单击**“添加”**。

    此时将出现**“添加新的域服务类”**对话框。

  5. 确保选中**“启用客户端访问”**复选框。

  6. 从**“可用 DataContext/ObjectContext 类”列表中,选择“Sales_DataEntities (实体框架)”**数据上下文对象。

    Tip提示:
    如果在创建实体模型时为数据连接提供其他名称,请选择包含 SalesOrderHeader 实体的数据上下文对象。
  7. 在**“实体”下方,选中“SalesOrderHeader”**实体复选框。

  8. 单击**“确定”**。

    这将生成域服务类。

  9. 重复本节中前面的步骤以创建另一个域服务,将它命名为 CustomerDomainService.cs(或 CustomerDomainService.vb),选择**“Customer_DataEntities”数据上下文对象,并选中“Customer”**实体复选框。

  10. 生成解决方案。

  11. 在客户端项目的 Generated_Code 文件夹中,打开生成的代码 SharedEntityExample.Web.g.cs 文件(您必须显示所有内容才能看到此文件(它在默认情况下处于隐藏状态)),您会发现,该位置有一个 SalesDomainContext 和一个 CustomerDomainContext。您将使用两个域上下文对象来加载关联数据。

  12. 关闭生成的代码文件。

在服务器项目中定义与代码的关联

您当前具有两个单独的实体模型和两个域服务,每个域服务分别公开了一个实体。可以通过调用适当的域服务来从任一实体单独加载数据。但若要加载这两个实体中的数据的组合,您必须定义这两个实体之间的关系。下列步骤演示如何在服务器项目中定义该关系。

在服务器项目中定义关联

  1. 右击服务器项目,依次选择**“添加”“新建项”**。

  2. 在类别列表中,选择**“Web”,然后选择“类”**模板。

  3. 将该类命名为 SalesOrderHeader.cs(或 SalesOrderHeader.vb),然后单击**“添加”**。

  4. SalesOrderHeader 类文件中,将 partial 关键字添加到类声明。

    Partial Public Class SalesOrderHeader
    
    End Class
    
    namespace SharedEntityExample.Web
    {
        public partial class SalesOrderHeader
        {
        }
    }
    
  5. 添加名为 Customer 的属性,该属性将返回 Customer 类型的对象。

    Partial Public Class SalesOrderHeader
        Public Property Customer() As Customer
    
    End Class
    
    namespace SharedEntityExample.Web
    {
        public partial class SalesOrderHeader
        {
            public Customer Customer { get; set; }
        }
    }
    
  6. System.ServiceModel.DomainServicesSystem.ComponentModel.DataAnnotations 命名空间添加 using(或 Imports)语句。

  7. ExternalReferenceAttribute 特性添加到 Customer 属性。

  8. AssociationAttribute 特性添加到具有以下值的 Customer 属性。

    Imports System.ServiceModel.DomainServices
    Imports System.ComponentModel.DataAnnotations
    
    Partial Public Class SalesOrderHeader
        <ExternalReference()> _
        <Association("Sales_Customer", "CustomerID", "CustomerID")> _
        Public Property Customer() As Customer
    
    End Class
    
    using System;
    using System.ServiceModel.DomainServices;
    using System.ComponentModel.DataAnnotations;
    
    namespace SharedEntityExample.Web
    {
        public partial class SalesOrderHeader
        {
            [ExternalReference]
            [Association("Sales_Customer", "CustomerID", "CustomerID")]
            public Customer Customer { get; set; }
        }
    }
    
  9. 生成解决方案。

  10. 在客户端项目的 Generated_Code 文件夹中,打开生成的代码文件,您会发现,SalesOrderHeader 类此时包含了具有 ExternalReferenceAttributeAssociationAttribute 特性的 Customer 属性。

  11. 关闭生成的代码文件。

从两个实体加载数据

在将引用的实体加载到其原始域上下文中之前,从另一个域上下文中引用实体的属性将为 null。不自动加载引用的实体。您必须先通过实体的原始域上下文加载实体,然后再访问交叉引用的实体。

从两个实体加载数据

  1. 在客户端项目中,打开 MainPage.xaml 文件。

  2. 从工具箱中,将 DataGrid 控件拖动到 Grid 元素内。

    将添加 XML 命名空间和对 Data 程序集的引用。

  3. 命名 DataGridSalesGrid 并定义要显示组合数据的列,如下面的 XAML 所示。

    <UserControl  x:Class="SharedEntityExample.MainPage"
        xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" 
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        d:DesignHeight="300" d:DesignWidth="400">
    
        <Grid x:Name="LayoutRoot" Background="White">
            <data:DataGrid Name="SalesGrid" AutoGenerateColumns="False">
                <data:DataGrid.Columns>
                    <data:DataGridTextColumn Header="Sales Order ID" Binding="{Binding SalesOrderID}"></data:DataGridTextColumn>
                    <data:DataGridTextColumn Header="Total Due" Binding="{Binding TotalDue}"></data:DataGridTextColumn>
                    <data:DataGridTextColumn Header="Order Date" Binding="{Binding OrderDate}"></data:DataGridTextColumn>
                    <data:DataGridTextColumn Header="Customer First Name" Binding="{Binding Customer.FirstName}"></data:DataGridTextColumn>
                    <data:DataGridTextColumn Header="Last Name" Binding="{Binding Customer.LastName}"></data:DataGridTextColumn>
                </data:DataGrid.Columns>
            </data:DataGrid>
        </Grid>
    </UserControl>
    
  4. 打开代码隐藏文件 MainPage.xaml.cs(或 MainPage.xaml.vb)。

  5. SharedEntityExample.Web 命名空间和 System.ServiceModel.DomainServices.Client 命名空间添加 using(或 Imports)语句。

  6. 创建 SalesDomainContextCustomerDomainContext 的实例的变量。

    Private salesContext As New SalesDomainContext()
    Private customerContext As New CustomerDomainContext()
    
    private SalesDomainContext salesContext = new SalesDomainContext();
    private CustomerDomainContext customerContext = new CustomerDomainContext();
    
  7. 在构造函数中,通过调用 AddReference 方法来添加域上下文对象之间的引用,通过调用 Load 方法来加载每个实体,并将销售实体设置为 DataGridItemsSource

    Imports SharedEntityExample.Web
    Imports System.ServiceModel.DomainServices.Client
    
    Partial Public Class MainPage
        Inherits UserControl
    
        Private salesContext As New SalesDomainContext()
        Private customerContext As New CustomerDomainContext()
    
        Public Sub New()
            InitializeComponent()
    
            salesContext.AddReference(GetType(Customer), customerContext)
    
            Dim salesLoadOp = salesContext.Load(salesContext.GetSalesOrderHeadersQuery())
            Dim customerLoadOp = customerContext.Load(customerContext.GetCustomersQuery())
    
            SalesGrid.ItemsSource = salesLoadOp.Entities
        End Sub
    
    End Class
    
    using System;
    using System.Windows.Controls;
    using SharedEntityExample.Web;
    using System.ServiceModel.DomainServices.Client;
    
    namespace SharedEntityExample
    {
        public partial class MainPage : UserControl
        {
            private SalesDomainContext salesContext = new SalesDomainContext();
            private CustomerDomainContext customerContext = new CustomerDomainContext();
    
            public MainPage()
            {
                InitializeComponent();
    
                salesContext.AddReference(typeof(Customer), customerContext);
    
                LoadOperation<SalesOrderHeader> salesLoadOp = salesContext.Load(salesContext.GetSalesOrderHeadersQuery());
                LoadOperation<Customer> customerLoadOp = customerContext.Load(customerContext.GetCustomersQuery());
    
                SalesGrid.ItemsSource = salesLoadOp.Entities;
            }
        }
    }
    
  8. 运行该解决方案。

    您将看到一个 DataGrid 实例,该实例显示了来自两个单独的实体模型的两个实体和域服务中的数据。

定义与客户端项目中的代码的关联

您还可以定义客户端上各实体间的关联,而无需向服务器项目添加任何代码。如果您觉得不在服务器项目中引入新的属性(其唯一用途是实现共同显示数据这一客户端目标)更可取,则此方法更佳。

定义与客户端项目中的代码的关联

  1. 在服务器项目中,删除(或注释掉)之前添加的整个 SalesOrderHeader.cs(或 SalesOrderHeader.vb)文件。

  2. 生成解决方案,使生成的代码文件不再具有 SalesOrderHeader 对象上的 Customer 属性。

  3. 在客户端项目中,添加一个名为 SalesOrderHeader.cs(或 SalesOrderHeader.vb)的新类文件。

  4. SalesOrderHeader 类文件中,将 partial 关键字添加到类声明,并将命名空间更改为 SharedEntityExample.Web。(如果您使用的是 Visual Basic,则可使用 Namespace 语句来指定 Web 命名空间。)

    此类将扩展生成的代码文件中的类。生成的实体类具有服务器项目中的命名空间。

  5. System.ServiceModel.DomainServicesSystem.ServiceModel.DomainServices.ClientSystem.ComponentModel.DataAnnotations 命名空间添加 using(对于 Visual Basic,则添加 Imports)语句。

  6. 若要建立关联,请定义 Customer 属性或 SalesOrderHeader 类,并用 ExternalReferenceAttributeAssociationAttribute 特性对其进行标记,如下面的代码示例所示。

    Imports System.ServiceModel.DomainServices
    Imports System.ServiceModel.DomainServices.Client
    Imports System.ComponentModel.DataAnnotations
    
    Namespace Web
    
        Partial Public Class SalesOrderHeader
            Private _customer As EntityRef(Of Customer)
    
            <ExternalReference()> _
            <Association("Sales_Customer", "CustomerID", "CustomerID")> _
            Public ReadOnly Property Customer() As Customer
                Get
                    If (Me._customer Is Nothing) Then
                        Me._customer = New EntityRef(Of Customer)(Me, "Customer", AddressOf Me.FilterCustomer)
                    End If
    
                    Return Me._customer.Entity
                End Get
            End Property
    
            Private Function FilterCustomer(ByVal entity As Customer) As Boolean
                Return (entity.CustomerID = Me.CustomerID)
            End Function
        End Class
    
    End Namespace
    
    using System;
    using System.Windows.Controls;
    using System.ServiceModel.DomainServices;
    using System.ComponentModel.DataAnnotations;
    using System.ServiceModel.DomainServices.Client;
    
    namespace SharedEntityExample.Web
    {
        public partial class SalesOrderHeader
        {
            private EntityRef<Customer> _customer;
    
            [ExternalReference]
            [Association("Sales_Customer", "CustomerID", "CustomerID")]
            public Customer Customer
            {
                get
                {
                    if (this._customer == null)
                    {
                        this._customer = new EntityRef<Customer>(this, "Customer", this.FilterCustomer);
                    }
    
                    return this._customer.Entity;
                }
            }
    
            private bool FilterCustomer(Customer entity)
            {
                return (entity.CustomerID == this.CustomerID);
            }
        }
    }
    
  7. 点击 F5 运行该解决方案。

    此时浏览器中应显示 DataGrid 实例,此实例显示了两个不同的实体模型中的每个实体在各自的域服务中共享的数据。