방법: 데이터 서비스 쿼리 결과 프로젝션(Silverlight 클라이언트)
프로젝션은 엔터티의 특정 속성만 응답에 반환되도록 지정하여 쿼리에서 반환되는 데이터 양을 줄이는 방식으로 Open Data Protocol(OData)에 정의되어 있습니다. 이렇게 하면 데이터 서비스에 있는 특정 엔터티 형식의 데이터를 클라이언트의 다른 엔터티 형식으로 프로젝션할 수 있습니다. select 절(Visual Basic의 경우 Select)을 사용하여 LINQ 쿼리의 결과에 대해 프로젝션을 수행할 수 있습니다. 또한 AddQueryOption 메서드를 호출하여 쿼리 URI에 $select 절을 추가할 수도 있습니다. 자세한 내용은 데이터 서비스 쿼리(WCF Data Services)를 참조하십시오.
WCF Data Services 퀵 스타트를 완료하면 응용 프로그램에서 액세스된 Northwind 샘플 데이터 서비스가 만들어집니다. OData 웹 사이트에 게시된 공용 Northwind 샘플 데이터 서비스를 사용할 수도 있습니다. 이 샘플 데이터 서비스는 읽기 전용이므로 변경 내용을 저장하려고 하면 오류가 발생합니다.
예
다음 예제에서는 Customer 엔터티를 클라이언트 응용 프로그램에 이미 정의되어 있는 CustomerAddress 형식으로 프로젝션하는 LINQ 쿼리를 보여 줍니다. CustomerAddress 클래스는 클라이언트에서 정의되며 클라이언트 라이브러리가 엔터티 형식으로 인식할 수 있도록 특성이 지정됩니다. 이 새 형식에는 주소 관련 속성과 ID 속성만 포함됩니다.
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("https://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
using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using ClientProjection.Northwind;
using System.Data.Services.Client;
namespace ClientProjection
{
public partial class MainPage : UserControl
{
// Create the binding collections and the data service context.
private DataServiceCollection<CustomerAddress> binding;
NorthwindEntities context;
CollectionViewSource customerAddressViewSource;
public MainPage()
{
InitializeComponent();
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
// Instantiate the binding collection.
binding = new DataServiceCollection<CustomerAddress>();
// Instantiate the context.
context =
new NorthwindEntities(new Uri("https://localhost:12345/Northwind.svc/"));
// Register the LoadCompleted event for the binding collection.
binding.LoadCompleted += new EventHandler<LoadCompletedEventArgs>(binding_LoadCompleted);
// Define an anonymous LINQ query that projects the Customers type into
// a CustomerAddress type that contains only address properties.
var query = from c in context.Customers
where c.Country == "Germany"
select new CustomerAddress
{
CustomerID = c.CustomerID,
Address = c.Address,
City = c.City,
PostalCode = c.PostalCode,
Country = c.Country
};
// Execute the query asynchronously.
binding.LoadAsync(query);
}
void binding_LoadCompleted(object sender, LoadCompletedEventArgs e)
{
if (e.Error == null)
{
// Load all pages of Customers before binding.
if (binding.Continuation != null)
{
binding.LoadNextPartialSetAsync();
}
else
{
//Load your data here and assign the result to the CollectionViewSource.
customerAddressViewSource =
(CollectionViewSource)this.Resources["customerAddressViewSource"];
customerAddressViewSource.Source = binding;
}
}
}
private void saveChangesButton_Click(object sender, RoutedEventArgs e)
{
try
{
// Start the saving changes operation.
context.BeginSaveChanges(OnChangesSaved, context);
}
catch (Exception ex)
{
MessageBox.Show(string.Format("The changes could not be saved to the data service.\n"
+ "The following error occurred: {0}", ex.Message));
}
}
private void OnChangesSaved(IAsyncResult result)
{
bool errorOccured = false;
// Use the Dispatcher to ensure that the
// asynchronous call returns in the correct thread.
Dispatcher.BeginInvoke(() =>
{
context = result.AsyncState as NorthwindEntities;
try
{
// Complete the save changes operation and display the response.
DataServiceResponse response = context.EndSaveChanges(result);
foreach (ChangeOperationResponse changeResponse in response)
{
if (changeResponse.Error != null) errorOccured = true;
}
if (!errorOccured)
{
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.");
}
}
catch (Exception ex)
{
// Display the error from the response.
MessageBox.Show(string.Format("The following error occured: {0}", ex.Message));
}
}
);
}
}
}
다음 예제에서는 이전 예제에 사용된 CustomerAddress 형식의 정의를 보여 줍니다.
[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
}
다음 XAML에서는 Silverlight 응용 프로그램의 기본 페이지를 정의합니다.
<UserControl x:Class="ClientProjection.MainPage"
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="312" d:DesignWidth="577"
xmlns:sdk="https://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>