Walkthrough: Sharing Entities between Multiple Domain Services
[WCF RIA Services Version 1 Service Pack 2 is compatible with either .NET framework 4 or .NET Framework 4.5, and with either Silverlight 4 or Silverlight 5.]
In your WCF RIA Services application, you may need to display data from a variety of data sources or expose an entity to more than one domain service. For example, an e-commerce Web site may need to integrate data from its order processing system with products from a third-party domain service. RIA Services enables this scenario by supporting references between entities from different DomainContext types. For more information on the nature and limitations of this feature, see the Shared Entities topic.
In this walkthrough, you will be shown two different ways of defining an association between entities in different domain context instances:
In the first part, the association is defined by adding code in the server project.
In the second part, the association is defined with code in the client project.
Both approaches to defining the association use the same code on the client to retrieve and display the data.
For illustrative purposes in this walkthrough, you will create two entity models and two domain services for exposing data in the AdventureWorksLT sample database. Typically, you would not create two entity models and two domain services to expose data from a single source. We are using this approach here to simplify the example while learning the technique that you can then use in more complicated scenarios that employ multiple data sources. For another example of displaying related data from a single data source, see Walkthrough: Displaying Related Data in a Silverlight Business Application.
Prerequisites
This and the other walkthroughs presented in the RIA Services documentation require several prerequisite programs, such as Visual Studio 2010 and the Silverlight Developer Runtime and SDK, be installed and configured properly, in addition to WCF RIA Services and the WCF RIA Services Toolkit. They also require installing and configuring SQL Server 2008 R2 Express with Advanced Services and installing the AdventureWorks OLTP and LT database.
Detailed instructions for the satisfaction of each of these prerequisites are provided by the topics within the Prerequisites for WCF RIA Services node. Follow the instructions provided there before proceeding with this walkthrough to ensure that you encounter as few problems as possible when working through this RIA Services walkthroughs.
Creating a solution, the data models, and domain services
To set up a RIA Services Solution
In Visual Studio 2010, create a new RIA Services project by selecting File, New, and then Project.
The New Project dialog box appears.
Select the Silverlight Application template from the Silverlight Templates and name the new project SharedEntityExample.
Click OK.
The New Silverlight Application dialog box appears.
Select the Enable WCF RIA Services check box at the bottom of the window.
Click OK to create the solution.
To create two entity data models
In Solution Explorer, right-click the server project (SharedEntityExample.Web), select Add, and then select New Item.
The Add New Item dialog box appears.
Select Data from the list of Installed Templates on the left, and then select the ADO.NET Entity Data Model template.
Name the new file SalesModel.edmx and click Add.
The Entity Data Model Wizard appears.
In the Choose Model Contents screen, select Generate from database and then click Next.
In the Choose Your Data Connection screen, create a data connection to the AdventureWorksLT database.
If the AdventureWorksLT database does not appear in the dropdown list, click on New Connection, select the correct Server name, and then select the AdventureWorksLT data base from the drop-down menu in the Connect to a database box lower down in the window. Select Test Connection button to make sure the database is accessible and click OK.
Make sure that the Save entity connection settings in Web.Config as check box is selected when you are returned to the Entity Data Model Wizard and change the value of the entity connection settings to Sales_DataEntities.
Click Next.
In the Choose Your Database Objects screen, select the SalesOrderHeader table.
Click Finish.
The entity model is created for the table.
Repeat the previous steps in this section to create another Entity Data Model for the AdventureWorksLT database, but name it CustomerModel.edmx, change the value of the entity connection setting in Web.config to Customer_DataEntities, and select the Customer (SalesLT) table.
Build the solution.
Open the code file for the Sales entity model, and notice that the SalesOrderHeader class has a CustomerID property. You will use this property to associate SalesOrderHeader and Customer.
To create the domain services
Right-click the SharedEntityExample.Web server project, select Add and New Item.
In the list of categories, select Web and then select the Domain Service Class template.
Name the class SalesDomainService.cs (or SalesDomainService.vb).
Click Add.
The Add New Domain Service Class dialog box appears.
Make sure the Enable client access check box is selected.
From the Available DataContext/ObjectContext classes list, select Sales_DataEntities (Entity Framework) data context object.
Tip
If you gave your data connection a different name when creating the entity model, select the data context object that contains the SalesOrderHeader entity.
Under Entities, select the SalesOrderHeader entity check box.
Click OK.
This generates the domain service class.
Repeat the previous steps in this section to create another domain service, but name it CustomerDomainService.cs (or CustomerDomainService.vb), select the Customer_DataEntities data context object, and select the Customer entity check box.
Build the solution.
In the client project, in the Generated_Code folder, open the generated code SharedEntityExample.Web.g.cs file (you will have to show all to see this file which is hidden by default) and notice that there is a SalesDomainContext and a CustomerDomainContext. You will use both domain context objects to load the associated data.
Close the generated code file.
Defining the association with code in the server project
You currently have two separate entity models and two domain services that each exposes one entity. You could load data from either entity separately by calling the appropriate domain service. But to load data that is a combination of data from both of the entities, you must define the relationship between these entities. The following steps show how to define that relationship in the server project.
To define the association in the server project
Right-click the server project, select Add and New Item.
In the list of categories, select Web and then select the Class template.
Name the class SalesOrderHeader.cs (or SalesOrderHeader.vb) and then click Add.
In the SalesOrderHeader class file, add the partial keyword to the class declaration.
Partial Public Class SalesOrderHeader End Class
namespace SharedEntityExample.Web { public partial class SalesOrderHeader { } }
Add a property named Customer that returns an object of the Customer type.
Partial Public Class SalesOrderHeader Public Property Customer() As Customer End Class
namespace SharedEntityExample.Web { public partial class SalesOrderHeader { public Customer Customer { get; set; } } }
Add a using (or Imports) statements for the System.ServiceModel.DomainServices and System.ComponentModel.DataAnnotations namespaces.
Add the ExternalReferenceAttribute attribute to the Customer property.
Add the AssociationAttribute attribute to the Customer property with the following values.
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; } } }
Build the solution.
In the client project, in the Generated_Code folder, open the generated code file and notice that the SalesOrderHeader class now contains a Customer property with the ExternalReferenceAttribute and AssociationAttribute attributes.
Close the generated code file.
Loading the data from both entities
Properties that reference an entity from another domain context will be null until the referenced entity is loaded in its original domain context. The referenced entity is not automatically loaded. You must load the entity through its origin domain context before accessing the cross-referenced entity.
To load the data from both entities
In the client project, open the MainPage.xaml file.
From the Toolbox, drag a DataGrid control to within the Grid element.
An XML namespace and references to Data assemblies are added.
Name DataGridSalesGrid and define columns to display the combined data as shown in the following 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>
Open the code-behind file MainPage.xaml.cs (or MainPage.xaml.vb).
Add a using (or Imports) statement for the SharedEntityExample.Web namespace and the System.ServiceModel.DomainServices.Client namespace.
Create variables for instances of the SalesDomainContext and CustomerDomainContext.
Private salesContext As New SalesDomainContext() Private customerContext As New CustomerDomainContext()
private SalesDomainContext salesContext = new SalesDomainContext(); private CustomerDomainContext customerContext = new CustomerDomainContext();
In the constructor, add a reference between the domain context objects by calling the AddReference method, load each entity by calling the Load method, and set the sales entities to the ItemsSource of the DataGrid.
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; } } }
Run the solution.
You will see a DataGrid instance that displays data from two entities in two separate entity models and domain services.
Defining the association with code in the client project
You can also define the association between the entities on the client, without having to add any code to the server project. This approach is better if you preferable not to introduce a new property in the server project whose only purpose is to achieve the client objective of displaying the data jointly.
To define the association with code in the client project
In the server project, delete (or comment out) the entire SalesOrderHeader.cs (or SalesOrderHeader.vb) file that you previously added.
Build the solution so that the generated code file no longer has a Customer property on the SalesOrderHeader object.
In the client project, add a new Class file named SalesOrderHeader.cs (or SalesOrderHeader.vb).
In the SalesOrderHeader class file, add the partial keyword to the class declaration, and change the namespace to SharedEntityExample.Web. (If you are using Visual Basic, you can specify the Web namespace by using the Namespace statement.)
This class extends the class in the generated code file. The generated entity class has the namespace from the server project.
Add a using (or Imports for Visual Basic) statement for the System.ServiceModel.DomainServices, System.ServiceModel.DomainServices.Client, and System.ComponentModel.DataAnnotations namespaces.
To establish the association, define the Customer property or the SalesOrderHeader class and mark it with the ExternalReferenceAttribute and AssociationAttribute attributes as shown in the following code sample.
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); } } }
Hit F5 to run the solution.
The DataGrid instance that displays data shared out from each of the entities in the two separate entity models in their respective domain services should now display in the browser.