Binding Data to Controls (WCF Data Services)
With WCF Data Services, you can bind controls such as the ComboBox and ListView controls to an instance of the DataServiceCollection<T> class. This collection, which inherits from the ObservableCollection<T> class, contains the data from an Open Data Protocol (OData) feed. This class represents a dynamic data collection that provides notifications when items get added or removed. When you use an instance of DataServiceCollection<T> for data binding, the WCF Data Services client libraries handle these events to ensure that objects tracked by the DataServiceContext remain synchronized with the data in the bound UI element.
The DataServiceCollection<T> class (indirectly) implements the INotifyCollectionChanged interface to alert the context when objects are added to or removed from the collection. Data service type objects used with a DataServiceCollection<T> must also implement the INotifyPropertyChanged interface to alert the DataServiceCollection<T> when properties of objects in the binding collection have changed.
Note
When you use the Add Service Reference dialog or theDataSvcUtil.exe tool with the /dataservicecollection option to generate the client data service classes, the generated data classes implement the INotifyPropertyChanged interface. For more information, see How to: Manually Generate Client Data Service Classes (WCF Data Services).
Creating the Binding Collection
Create a new instance of the DataServiceCollection<T> class by calling one of the class constructor methods with a supplied DataServiceContext instance and optionally a DataServiceQuery<TElement> or LINQ query that, when it is executed, returns an IEnumerable<T> instance. This IEnumerable<T> provides the source of objects for the binding collection, which are materialized from an OData feed. For more information, see Object Materialization (WCF Data Services). By default, changes made to bound objects and items inserted into the collection are automatically tracked by the DataServiceContext. If you need to manually track these changes, call one of the constructor methods that takes a trackingMode parameter and specify a value of None.
The following example shows how to create an instance of DataServiceCollection<T> based on a supplied DataServiceContext and a DataServiceQuery<TElement> that returns all customers with related orders:
' Create a new collection that contains all customers and related orders.
Dim trackedCustomers As DataServiceCollection(Of Customer) = _
New DataServiceCollection(Of Customer)(context.Customers.Expand("Orders"))
// Create a new collection that contains all customers and related orders.
DataServiceCollection<Customer> trackedCustomers =
new DataServiceCollection<Customer>(context.Customers.Expand("Orders"));
Binding Data to Windows Presentation Foundation Elements
Because the DataServiceCollection<T> class inherits from the ObservableCollection<T> class, you can bind objects to an element, or control, in a Windows Presentation Foundation (WPF) application as you would when using the ObservableCollection<T> class for binding. For more information, see Data Binding (Windows Presentation Foundation). One way to bind data service data to WPF controls is to set the DataContext property of the element to the instance of the DataServiceCollection<T> class that contains the query result. In this case, you use the ItemsSource property to set the object source for the control. Use the DisplayMemberPath property to specify which property of the bound object to display. If you are binding an element to a related object that is returned by a navigation property, include the path in the binding defined for the ItemsSource property. This path is relative to the root object set by the DataContext property of the parent control. The following example sets the DataContext property of a StackPanel element to bind the parent control to an DataServiceCollection<T> of customer objects:
' Create a LINQ query that returns customers with related orders.
Dim customerQuery = From cust In context.Customers.Expand("Orders") _
Where cust.Country = customerCountry _
Select cust
' Create a new collection for binding based on the LINQ query.
trackedCustomers = New DataServiceCollection(Of Customer)(customerQuery)
' Bind the root StackPanel element to the collection
' related object binding paths are defined in the XAML.
Me.LayoutRoot.DataContext = trackedCustomers
' Create a LINQ query that returns customers with related orders.
Dim customerQuery = From cust In context.Customers.Expand("Orders") _
Where cust.Country = customerCountry _
Select cust
' Create a new collection for binding based on the LINQ query.
trackedCustomers = New DataServiceCollection(Of Customers)(customerQuery, _
TrackingMode.AutoChangeTracking, "Customers", _
AddressOf OnMyPropertyChanged, AddressOf OnMyCollectionChanged)
' Bind the root StackPanel element to the collection
' related object binding paths are defined in the XAML.
Me.LayoutRoot.DataContext = trackedCustomers
Me.LayoutRoot.UpdateLayout()
' Create a LINQ query that returns customers with related orders.
Dim customerQuery = From cust In context.Customers.Expand("Orders") _
Where cust.Country = customerCountry _
Select cust
' Create a new collection for binding based on the LINQ query.
trackedCustomers = New DataServiceCollection(Of Customer)(customerQuery, _
TrackingMode.AutoChangeTracking, "Customers", _
AddressOf OnMyPropertyChanged, AddressOf OnMyCollectionChanged)
' Bind the root StackPanel element to the collection
' related object binding paths are defined in the XAML.
Me.LayoutRoot.DataContext = trackedCustomers
// Create a LINQ query that returns customers with related orders.
var customerQuery = from cust in context.Customers.Expand("Orders")
where cust.Country == customerCountry
select cust;
// Create a new collection for binding based on the LINQ query.
trackedCustomers = new DataServiceCollection<Customer>(customerQuery,
TrackingMode.AutoChangeTracking,"Customers",
OnPropertyChanged, OnCollectionChanged);
// Bind the root StackPanel element to the collection;
// related object binding paths are defined in the XAML.
this.LayoutRoot.DataContext = trackedCustomers;
// Create a LINQ query that returns customers with related orders.
var customerQuery = from cust in context.Customers.Expand("Orders")
where cust.Country == customerCountry
select cust;
// Create a new collection for binding based on the LINQ query.
trackedCustomers = new DataServiceCollection<Customer>(customerQuery);
// Bind the root StackPanel element to the collection;
// related object binding paths are defined in the XAML.
LayoutRoot.DataContext = trackedCustomers;
The following example shows the XAML binding definition of the child DataGrid and ComboBox controls:
<StackPanel Orientation="Vertical" Height="Auto" Name="LayoutRoot" Width="Auto">
<Label Content="Customer ID" Margin="20,0,0,0" />
<ComboBox Name="customerIDComboBox" DisplayMemberPath="CustomerID" ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="True" SelectedIndex="0" Height="23" Width="120"
HorizontalAlignment="Left" Margin="20,0,0,0" VerticalAlignment="Center" />
<ListView ItemsSource="{Binding Path=Orders}" Name="ordersDataGrid" Margin="34,46,34,50">
<ListView.View>
<GridView AllowsColumnReorder="False" ColumnHeaderToolTip="Line Items">
<GridViewColumn DisplayMemberBinding="{Binding Path=OrderID, Mode=OneWay}"
Header="Order ID" Width="50"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=OrderDate, Mode=TwoWay}"
Header="Order Date" Width="50"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=Freight, Mode=TwoWay}"
Header="Freight Cost" Width="50"/>
</GridView>
</ListView.View>
</ListView>
<Button Name="saveChangesButton" Content="Save Changes" Click="saveChangesButton_Click"
Width="80" Height="30" Margin="450,0,0,0"/>
</StackPanel>
For more information, see How to: Bind Data to Windows Presentation Foundation Elements (WCF Data Services).
When an entity participates in a one-to-many or many-to-many relationship, the navigation property for the relationship returns a collection of related objects. When you use the Add Service Reference dialog box or the DataSvcUtil.exe tool to generate the client data service classes, the navigation property returns an instance of DataServiceCollection<T>. This enables you to bind related objects to a control, and support common WPF binding scenarios, such as the master-detail binding pattern for related entities. In the previous XAML example, the XAML code binds the master DataServiceCollection<T> to the root data element. The order DataGrid is then bound to the Orders DataServiceCollection<T> returned from the selected Customers object, which is in turn bound to the root data element of the Window.
Binding Data to Windows Forms Controls
To bind objects to a Windows Form control, set the DataSource property of the control to the instance of the DataServiceCollection<T> class that contains the query result.
Note
Data binding is only supported for controls that listen for change events by implementing the INotifyCollectionChanged and INotifyPropertyChanged interfaces. When a control does not support this kind of change notification, changes that are made to the underlying DataServiceCollection<T> are not reflected in the bound control.
The following example binds an DataServiceCollection<T> to a ComboBox control:
' Create a new collection for binding based on the LINQ query.
trackedCustomers = New DataServiceCollection(Of Customer)(customerQuery)
'Bind the Customers combobox to the collection.
customersComboBox.DisplayMember = "CustomerID"
customersComboBox.DataSource = trackedCustomers
// Create a new collection for binding based on the LINQ query.
trackedCustomers = new DataServiceCollection<Customer>(customerQuery);
//Bind the Customers combobox to the collection.
customersComboBox.DisplayMember = "CustomerID";
customersComboBox.DataSource = trackedCustomers;
When you use the Add Service Reference dialog to generate the client data service classes, a project data source is also created that is based on the generated DataServiceContext. With this data source, you can create UI elements or controls that display data from the data service simply by dragging items from the Data Sources window onto the designer. These items become elements in the application UI that are bound to the data source. For more information, see How to: Bind Data Using a Project Data Source (WCF Data Services).
Binding Paged Data
A data service can be configured to limit the amount of queried data that is returned in a single response message. For more information, see Configuring the Data Service (WCF Data Services). When the data service is paging response data, each response contains a link that is used to return the next page of results. For more information, see Loading Deferred Content (WCF Data Services). In this case, you must explicitly load pages by calling the Load method on the DataServiceCollection<T> by passing the URI obtained from the NextLinkUri property, as in the following example:
' Create a new collection for binding based on the LINQ query.
trackedCustomers = New DataServiceCollection(Of Customer)(customerQuery)
' Load all pages of the response at once.
While trackedCustomers.Continuation IsNot Nothing
trackedCustomers.Load( _
context.Execute(Of Customer)(trackedCustomers.Continuation.NextLinkUri))
End While
// Create a new collection for binding based on the LINQ query.
trackedCustomers = new DataServiceCollection<Customer>(customerQuery);
// Load all pages of the response at once.
while (trackedCustomers.Continuation != null)
{
trackedCustomers.Load(
context.Execute<Customer>(trackedCustomers.Continuation.NextLinkUri));
}
Related objects are loaded in a similar manner. For more information, see How to: Bind Data to Windows Presentation Foundation Elements (WCF Data Services).
Customizing Data Binding Behaviors
The DataServiceCollection<T> class enables you to intercept the events raised when changes are made to the collection, such as an object being added or removed, and when changes are made to the properties of object in a collection. You can modify the data binding events to override the default behavior, which includes the following constraints:
No validation is performed within the delegates.
Adding an entity automatically adds related entities.
Deleting an entity does not delete the related entities.
When you create a new instance of DataServiceCollection<T>, you have the option to specify the following parameters that define delegates to methods that handle the events raised when bound objects are changed:
entityChanged - a method that is called when the property of a bound object is changed. This Func<T, TResult> delegate accepts an EntityChangedParams object and returns a Boolean value that indicates whether the default behavior, to call UpdateObject on the DataServiceContext, should still occur.
entityCollectionChanged - a method that is called when an object is added or removed from the binding collection. This Func<T, TResult> delegate accepts an EntityCollectionChangedParams object and returns a Boolean value that indicates whether the default behavior, to call AddObject for an Add action or DeleteObject for a Remove action on the DataServiceContext, should still occur.
Note
WCF Data Services performs no validation of the custom behaviors that you implement in these delegates.
In the following example, the Remove action is customized to call the DeleteLink and DeleteObject method to remove Orders_Details entities that belong to a deleted Orders entity. This custom action is performed because dependent entities are not automatically deleted when the parent entity is deleted.
' Method that is called when the CollectionChanged event is handled.
Private Function OnMyCollectionChanged( _
ByVal entityCollectionChangedinfo As EntityCollectionChangedParams) As Boolean
If entityCollectionChangedinfo.Action = _
NotifyCollectionChangedAction.Remove Then
' Delete the related items when an order is deleted.
If entityCollectionChangedinfo.TargetEntity.GetType() Is GetType(Orders) Then
' Get the context and object from the supplied parameter.
Dim context = entityCollectionChangedinfo.Context
Dim deletedOrder As Orders = _
CType(entityCollectionChangedinfo.TargetEntity, Orders)
' Load the related OrderDetails.
context.LoadProperty(deletedOrder, "Order_Details")
' Delete the order and its related items
For Each item As Order_Details In deletedOrder.Order_Details
'context.DeleteLink(deletedOrder, "Order_Details", item)
context.DeleteObject(item)
Next
' Delete the order and then return false since the object is already deleted.
context.DeleteObject(deletedOrder)
Return False
Else
Return True
End If
Else
' Use the default behavior.
Return True
End If
End Function
' Method that is called when the CollectionChanged event is handled.
Private Function OnMyCollectionChanged( _
ByVal entityCollectionChangedinfo As EntityCollectionChangedParams) As Boolean
If entityCollectionChangedinfo.Action = _
NotifyCollectionChangedAction.Remove Then
' Delete the related items when an order is deleted.
If entityCollectionChangedinfo.TargetEntity.GetType() Is GetType(Order) Then
' Get the context and object from the supplied parameter.
Dim context = entityCollectionChangedinfo.Context
Dim deletedOrder As Order = _
CType(entityCollectionChangedinfo.TargetEntity, Order)
If deletedOrder.Order_Details.Count = 0 Then
' Load the related OrderDetails.
context.LoadProperty(deletedOrder, "Order_Details")
End If
' Delete the order and its related items
For Each item As Order_Detail In deletedOrder.Order_Details
context.DeleteObject(item)
Next
' Delete the order and then return false since the object is already deleted.
context.DeleteObject(deletedOrder)
Return True
Else
Return False
End If
Else
' Use the default behavior.
Return False
End If
End Function
// Method that is called when the CollectionChanged event is handled.
private bool OnCollectionChanged(
EntityCollectionChangedParams entityCollectionChangedinfo)
{
if (entityCollectionChangedinfo.Action ==
NotifyCollectionChangedAction.Remove)
{
// Delete the related items when an order is deleted.
if (entityCollectionChangedinfo.TargetEntity.GetType() == typeof(Order))
{
// Get the context and object from the supplied parameter.
DataServiceContext context = entityCollectionChangedinfo.Context;
Order deletedOrder = entityCollectionChangedinfo.TargetEntity as Order;
if (deletedOrder.Order_Details.Count == 0)
{
// Load the related OrderDetails.
context.LoadProperty(deletedOrder, "Order_Details");
}
// Delete the order and its related items;
foreach (Order_Detail item in deletedOrder.Order_Details)
{
context.DeleteObject(item);
}
// Delete the order and then return true since the object is already deleted.
context.DeleteObject(deletedOrder);
return true;
}
else
{
return false;
}
}
else
{
// Use the default behavior.
return false;
}
}
For more information, see How to: Customize Data Binding Behaviors (WCF Data Services).
The default behavior when an object is removed from a DataServiceCollection<T> by using the Remove method is that the object is also marked as deleted in the DataServiceContext. To change this behavior, you can specify a delegate to a method in the entityCollectionChanged parameter that is called when the CollectionChanged event occurs.
Data Binding with Custom Client Data Classes
To be able to load objects into a DataServiceCollection<T>, the objects themselves must implement the INotifyPropertyChanged interface. Data service client classes that are generated when you use the Add Service Reference dialog box or the DataSvcUtil.exe tool implement this interface. If you provide your own client data classes, you must use another type of collection for data binding. When objects change, you must handle events in the data bound controls to call the following methods of the DataServiceContext class:
AddObject - when a new object is added to the collection.
DeleteObject - when an object is removed from the collection.
UpdateObject - when a property is changed on an object in the collection.
AddLink - when an object is added to a collection of related object.
SetLink - when an object is added to a collection of related objects.
For more information, see Updating the Data Service (WCF Data Services).
See Also
Tasks
How to: Manually Generate Client Data Service Classes (WCF Data Services)