Presentation Models
[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.]
WCF RIA Services enables you to create data models that aggregate data from multiple entities in the data access layer that are known as a presentation model. You use this feature when you do not want to directly expose the entities in your data access layer to the client. When using a presentation model, you can respond to changes in the data access layer by changing only the presentation model and not the client. Also, you can simplify the client code by designing a model that aggregates only those fields that are relevant to users of the client. This topic describes how to create, query and update a presentation model and how to pass values back to the client when changes have been set in either middle-tier or in the data source.
Creating the Presentation Model
The database structure that is needed to maintain data integrity may be more complicated than what you need for the entities in your client application. You can create a presentation model that simplifies this data structure by combining the fields that are relevant to your application into one presentation model. For example, in the AdventureWorksLT sample database, you retrieve customer and address data through the Customer, CustomerAddress, and Address tables.
You create a presentation model by creating a class in the server project, and defining the properties you want to make available. The properties you define correspond to the properties you want to expose from the entities. For example, you can create the following CustomerPresentationModel class in the server project to present only the field you want from the Customer, CustomerAddress, and Address tables.
Public Class CustomerPresentationModel
<Key()> _
Public Property CustomerID As Integer
Public Property FirstName As String
Public Property LastName As String
Public Property EmailAddress As String
Public Property Phone As String
Public Property AddressType As String
Public Property AddressLine1 As String
Public Property AddressLine2 As String
Public Property City As String
Public Property StateProvince As String
Public Property PostalCode As String
Public Property AddressID As Integer
Public Property AddressModifiedDate As DateTime
Public Property CustomerModifiedDate As DateTime
End Class
public class CustomerPresentationModel
{
[Key]
public int CustomerID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public string Phone { get; set; }
public string AddressType { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string StateProvince { get; set; }
public string PostalCode { get; set; }
public int AddressID { get; set; }
public DateTime AddressModifiedDate { get; set; }
public DateTime CustomerModifiedDate { get; set; }
}
Querying and Modifying Values in the Presentation Model
After creating the presentation model, you expose it to the client project by adding a domain service that interacts with the presentation type. The entity values are only exposed through this domain service, and are not exposed through a domain service that exposes the whole entity. The following example shows a domain service that derives from the DomainService class.
[EnableClientAccess()]
public class CustomerDomainService : DomainService
{
AdventureWorksLT_DataEntities context = new AdventureWorksLT_DataEntities();
}
To retrieve data, you add a query method to the domain service. In the query method, you retrieve the relevant data from the entities in the data access layer and set those values to the corresponding properties in a new instance of the presentation model. From the query method, you return either an instance of the presentation model type or an [T:System.Linq.IQueryable’1] where the generic type is your CustomerPresentationModel type. The following example shows a query method for the customer presentation model.
Public Function GetCustomersWithMainOffice() As IQueryable(Of CustomerPresentationModel)
Return From c In context.Customers
Join ca In context.CustomerAddresses On c.CustomerID Equals ca.CustomerID
Join a In context.Addresses On ca.AddressID Equals a.AddressID
Where ca.AddressType = "Main Office"
Select New CustomerPresentationModel() With _
{
.CustomerID = c.CustomerID,
.FirstName = c.FirstName,
.LastName = c.LastName,
.EmailAddress = c.EmailAddress,
.Phone = c.Phone,
.AddressType = ca.AddressType,
.AddressLine1 = a.AddressLine1,
.AddressLine2 = a.AddressLine2,
.City = a.City,
.StateProvince = a.StateProvince,
.PostalCode = a.PostalCode,
.AddressID = a.AddressID,
.AddressModifiedDate = a.ModifiedDate,
.CustomerModifiedDate = c.ModifiedDate
}
End Function
public IQueryable<CustomerPresentationModel> GetCustomersWithMainOffice()
{
return from c in context.Customers
join ca in context.CustomerAddresses on c.CustomerID equals ca.CustomerID
join a in context.Addresses on ca.AddressID equals a.AddressID
where ca.AddressType == "Main Office"
select new CustomerPresentationModel()
{
CustomerID = c.CustomerID,
FirstName = c.FirstName,
LastName = c.LastName,
EmailAddress = c.EmailAddress,
Phone = c.Phone,
AddressType = ca.AddressType,
AddressLine1 = a.AddressLine1,
AddressLine2 = a.AddressLine2,
City = a.City,
StateProvince = a.StateProvince,
PostalCode = a.PostalCode,
AddressID = a.AddressID,
AddressModifiedDate = a.ModifiedDate,
CustomerModifiedDate = c.ModifiedDate
};
}
Because the (Customer, CustomerAddress, and Address) entities in the data access layer are not exposed by the domain service, those types are not generated in the client project. Instead, only the CustomerPresentationModel type is generated in the client project.
To update data through the presentation model, you create an update method and define the logic for saving the values from the presentation model to the entities. An example of an update method is shown at the end of the next section.
Passing Values Back to the Client
After submitting changes, you may want to pass values back to the client that are set in either middle-tier logic or in the data source. RIA Services provides the [M:System.ServiceModel.DomainServices.Server.ChangeSet.Associate``2(``0,``1,System.Action`2)] method to map values from the entity back to the presentation model. In this method, you provide a callback method that is called after the changes have been submitted. In the callback method, you assign any values to the presentation model that have been modified in the middle tier. You perform this step to ensure that the client possesses the current values.
The following example shows how to update values in the entities and how to map modified data back to the presentation model.
<Update()> _
Public Sub UpdateCustomer(ByVal customerPM As CustomerPresentationModel)
Dim customerEntity As Customer = context.Customers.Where(Function(c) c.CustomerID = customerPM.CustomerID).FirstOrDefault()
Dim customerAddressEntity As CustomerAddress = context.CustomerAddresses.Where(Function(ca) ca.CustomerID = customerPM.CustomerID And ca.AddressID = customerPM.AddressID).FirstOrDefault()
Dim addressEntity As Address = context.Addresses.Where(Function(a) a.AddressID = customerPM.AddressID).FirstOrDefault()
customerEntity.FirstName = customerPM.FirstName
customerEntity.LastName = customerPM.LastName
customerEntity.EmailAddress = customerPM.EmailAddress
customerEntity.Phone = customerPM.Phone
customerAddressEntity.AddressType = customerPM.AddressType
addressEntity.AddressLine1 = customerPM.AddressLine1
addressEntity.AddressLine2 = customerPM.AddressLine2
addressEntity.City = customerPM.City
addressEntity.StateProvince = customerPM.StateProvince
addressEntity.PostalCode = customerPM.PostalCode
Dim originalValues As CustomerPresentationModel = Me.ChangeSet.GetOriginal(customerPM)
If (originalValues.FirstName <> customerPM.FirstName Or
originalValues.LastName <> customerPM.LastName Or
originalValues.EmailAddress <> customerPM.EmailAddress Or
originalValues.Phone <> customerPM.Phone) Then
customerEntity.ModifiedDate = DateTime.Now
End If
If (originalValues.AddressLine1 <> customerPM.AddressLine1 Or
originalValues.AddressLine2 <> customerPM.AddressLine2 Or
originalValues.City <> customerPM.City Or
originalValues.StateProvince <> customerPM.StateProvince Or
originalValues.PostalCode <> customerPM.PostalCode) Then
addressEntity.ModifiedDate = DateTime.Now
End If
context.SaveChanges()
Me.ChangeSet.Associate(customerPM, customerEntity, AddressOf MapCustomerToCustomerPM)
Me.ChangeSet.Associate(customerPM, addressEntity, AddressOf MapAddressToCustomerPM)
End Sub
Private Sub MapCustomerToCustomerPM(ByVal customerPM As CustomerPresentationModel, ByVal customerEntity As Customer)
customerPM.CustomerModifiedDate = customerEntity.ModifiedDate
End Sub
Private Sub MapAddressToCustomerPM(ByVal customerPM As CustomerPresentationModel, ByVal addressEntity As Address)
customerPM.AddressModifiedDate = addressEntity.ModifiedDate
End Sub
[Update]
public void UpdateCustomer(CustomerPresentationModel customerPM)
{
Customer customerEntity = context.Customers.Where(c => c.CustomerID == customerPM.CustomerID).FirstOrDefault();
CustomerAddress customerAddressEntity = context.CustomerAddresses.Where(ca => ca.CustomerID == customerPM.CustomerID && ca.AddressID == customerPM.AddressID).FirstOrDefault();
Address addressEntity = context.Addresses.Where(a => a.AddressID == customerPM.AddressID).FirstOrDefault();
customerEntity.FirstName = customerPM.FirstName;
customerEntity.LastName = customerPM.LastName;
customerEntity.EmailAddress = customerPM.EmailAddress;
customerEntity.Phone = customerPM.Phone;
customerAddressEntity.AddressType = customerPM.AddressType;
addressEntity.AddressLine1 = customerPM.AddressLine1;
addressEntity.AddressLine2 = customerPM.AddressLine2;
addressEntity.City = customerPM.City;
addressEntity.StateProvince = customerPM.StateProvince;
addressEntity.PostalCode = customerPM.PostalCode;
CustomerPresentationModel originalValues = this.ChangeSet.GetOriginal(customerPM);
if (originalValues.FirstName != customerPM.FirstName ||
originalValues.LastName != customerPM.LastName ||
originalValues.EmailAddress != customerPM.EmailAddress ||
originalValues.Phone != customerPM.Phone)
{
customerEntity.ModifiedDate = DateTime.Now;
}
if (originalValues.AddressLine1 != customerPM.AddressLine1 ||
originalValues.AddressLine2 != customerPM.AddressLine2 ||
originalValues.City != customerPM.City ||
originalValues.StateProvince != customerPM.StateProvince ||
originalValues.PostalCode != customerPM.PostalCode)
{
addressEntity.ModifiedDate = DateTime.Now;
}
context.SaveChanges();
this.ChangeSet.Associate(customerPM, customerEntity, MapCustomerToCustomerPM);
this.ChangeSet.Associate(customerPM, addressEntity, MapAddressToCustomerPM);
}
private void MapCustomerToCustomerPM(CustomerPresentationModel customerPM, Customer customerEntity)
{
customerPM.CustomerModifiedDate = customerEntity.ModifiedDate;
}
private void MapAddressToCustomerPM(CustomerPresentationModel customerPM, Address addressEntity)
{
customerPM.AddressModifiedDate = addressEntity.ModifiedDate;
}