How to: Project Data Service Query Results (WCF Data Services/Silverlight)
Projection is defined in the Open Data Protocol (OData) as a way to reduce the amount of data returned by a query by specifying that only certain properties of an entity are returned in the response. This enables you to project the data from an entity type in the data service into a different entity type at the client. You can perform projections on the results of a LINQ query by using the select clause (Select in Visual Basic). You can also call the AddQueryOption method to add a $select clause to the query URI. For more information, see Querying the Data Service (WCF Data Services).
The Northwind sample data service accessed by the application is created when you complete the procedures in the topic How to: Create the Northwind Data Service (WCF Data Services/Silverlight). You can also use the Northwind sample data service that is published on the OData Web site; this sample data service is read-only and attempting to save changes returns an error.
The following example shows a LINQ query that projects Customer entities into the CustomerAddress type that has already been defined in the client application. The CustomerAddress class is defined on the client and is attributed so that the client library can recognize it as an entity type. This new type contains only address-specific properties plus the identity property.
Imports System Imports System.Linq Imports System.Windows Imports System.Windows.Controls Imports System.Windows.Data Imports ClientProjection.Northwind Imports System.Data.Services.Client Partial Public Class MainPage Inherits UserControl ' Create the binding collections and the data service context. Dim binding As DataServiceCollection(Of CustomerAddress) Dim context As NorthwindEntities Dim customerAddressViewSource As CollectionViewSource Public Sub Main() InitializeComponent() End Sub Private Sub MainPage_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs) 'Instantiate the binding collection. binding = New DataServiceCollection(Of CustomerAddress)() ' Instantiate the context. context = _ New NorthwindEntities(New Uri("http://localhost:54321/Northwind.svc/")) ' Define a handler for the LoadCompleted event of the collection. AddHandler binding.LoadCompleted, _ AddressOf binding_LoadCompleted ' Define an anonymous LINQ query that projects the Customers type into ' a CustomerAddress type that contains only address properties. Dim query = From c In context.Customers _ Where c.Country = "Germany" _ Select New CustomerAddress With {.CustomerID = c.CustomerID, _ .Address = c.Address, _ .City = c.City, _ .PostalCode = c.PostalCode, _ .Country = c.Country _ } ' Execute the query asynchronously. binding.LoadAsync(query) End Sub Private Sub binding_LoadCompleted(ByVal sender As Object, ByVal e As LoadCompletedEventArgs) If e.Error Is Nothing Then ' Load all pages of Customers before binding. If Not binding.Continuation Is Nothing Then binding.LoadNextPartialSetAsync() Else ' Load your data here and assign the result to the CollectionViewSource. customerAddressViewSource = CType(Me.Resources("customerAddressViewSource"), CollectionViewSource) customerAddressViewSource.Source = binding End If End If End Sub ' We need to persist the result of an operation ' to be able to invoke the dispatcher. Private currentResult As IAsyncResult Private Sub saveChangesButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) ' Define the delegate to callback into the process Dim callback As AsyncCallback = AddressOf OnChangesSaved Try ' Start the saving changes operation. This needs to be a ' batch operation in case we are added a new object with ' a new relationship. context.BeginSaveChanges(SaveChangesOptions.Batch, _ callback, context) Catch ex As Exception MessageBox.Show(String.Format( _ "The changes could not be saved to the data service.\n" _ & "The following error occurred: {0}", ex.Message)) End Try End Sub Private Sub OnChangesSaved(ByVal result As IAsyncResult) ' Persist the result for the delegate. currentResult = result ' Use the Dispatcher to ensure that the ' asynchronous call returns in the correct thread. Dispatcher.BeginInvoke(AddressOf ChangesSavedByDispatcher) End Sub Private Sub ChangesSavedByDispatcher() Dim errorOccured As Boolean = False context = CType(currentResult.AsyncState, NorthwindEntities) Try ' Complete the save changes operation and display the response. Dim response As DataServiceResponse = _ context.EndSaveChanges(currentResult) For Each changeResponse As ChangeOperationResponse In response If changeResponse.Error IsNot Nothing Then errorOccured = True Next If Not errorOccured Then MessageBox.Show("The changes have been saved to the data service.") Else MessageBox.Show("An error occured. One or more changes could not be saved.") End If Catch ex As Exception ' Display the error from the response. MessageBox.Show(String.Format("The following error occured: {0}", ex.Message)) End Try End Sub End Class
The following example shows the definition of the CustomerAddress type that is used in the previous example.
[DataServiceKey("CustomerID")] public partial class CustomerAddress : INotifyPropertyChanged { private string _customerID; private string _address; private string _city; private string _postalCode; private string _country; public string CustomerID { get { return this._customerID; } set { this.OnCustomerIDChanging(value); this._customerID = value; this.OnCustomerIDChanged(); this.OnPropertyChanged("CustomerID"); } } public string Address { get { return this._address; } set { this.OnAddressChanging(value); this._address = value; this.OnAddressChanged(); this.OnPropertyChanged("Address"); } } public string City { get { return this._city; } set { this.OnCityChanging(value); this._city = value; this.OnCityChanged(); this.OnPropertyChanged("City"); } } public string PostalCode { get { return this._postalCode; } set { this.OnPostalCodeChanging(value); this._postalCode = value; this.OnPostalCodeChanged(); this.OnPropertyChanged("PostalCode"); } } public string Country { get { return this._country; } set { this.OnCountryChanging(value); this._country = value; this.OnCountryChanged(); this.OnPropertyChanged("Country"); } } #region INotifyPropertyChanged implementation // Members required by the INotifyPropertyChanged implementation. public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string property) { if ((this.PropertyChanged != null)) { this.PropertyChanged(this, new PropertyChangedEventArgs(property)); } } partial void OnCustomerIDChanging(string value); partial void OnCustomerIDChanged(); partial void OnAddressChanging(string value); partial void OnAddressChanged(); partial void OnCityChanging(string value); partial void OnCityChanged(); partial void OnRegionChanging(string value); partial void OnRegionChanged(); partial void OnPostalCodeChanging(string value); partial void OnPostalCodeChanged(); partial void OnCountryChanging(string value); partial void OnCountryChanged(); #endregion }
The following XAML defines the main page of the Silverlight application.
<UserControl x:Class="ClientProjection.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="312" d:DesignWidth="577"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:my="clr-namespace:ClientProjection" Loaded="MainPage_Loaded">
<UserControl.Resources>
<CollectionViewSource x:Key="customerAddressViewSource"
d:DesignSource="{d:DesignInstance my:CustomerAddress, CreateList=True}" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White" DataContext="" Height="312" Width="577"
VerticalAlignment="Top" HorizontalAlignment="Left">
<Grid.RowDefinitions>
<RowDefinition Height="203*" />
<RowDefinition Height="119*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="17*" />
<ColumnDefinition Width="336*" />
</Grid.ColumnDefinitions>
<sdk:DataGrid AutoGenerateColumns="False" Height="233" HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource customerAddressViewSource}}"
Name="customerAddressDataGrid" RowDetailsVisibilityMode="VisibleWhenSelected"
VerticalAlignment="Top" Width="553" Margin="12,24,0,0"
Grid.RowSpan="2" Grid.ColumnSpan="2">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn x:Name="customerIDColumn" Binding="{Binding Path=CustomerID}"
Header="Customer" Width="80" />
<sdk:DataGridTextColumn x:Name="addressColumn" Binding="{Binding Path=Address}"
Header="Address" Width="180" />
<sdk:DataGridTextColumn x:Name="cityColumn" Binding="{Binding Path=City}"
Header="City" Width="120" />
<sdk:DataGridTextColumn x:Name="countryColumn" Binding="{Binding Path=Country}"
Header="Country" Width="80" />
<sdk:DataGridTextColumn x:Name="postalCodeColumn" Binding="{Binding Path=PostalCode}"
Header="Postal Code" Width="90" />
</sdk:DataGrid.Columns>
</sdk:DataGrid>
<Button Content="Save" Grid.Column="1" HorizontalAlignment="Left"
Margin="462,78,0,0" Name="saveChangesButton" Width="75" Grid.Row="1"
Click="saveChangesButton_Click" Height="25" VerticalAlignment="Top" />
</Grid>
</UserControl>