Binding Data to Controls (WCF Data Services)

Important

WCF Data Services has been deprecated and will no longer be available for download from the Microsoft Download Center. WCF Data Services supported earlier versions of the Microsoft OData (V1-V3) protocol only and has not been under active development. OData V1-V3 has been superseded by OData V4, which is an industry standard published by OASIS and ratified by ISO. OData V4 is supported through the OData V4 compliant core libraries available at Microsoft.OData.Core. Support documentation is available at OData.Net, and the OData V4 service libraries are available at Microsoft.AspNetCore.OData.

RESTier is the successor to WCF Data Services. RESTier helps you bootstrap a standardized, queryable, HTTP-based REST interface in minutes. Like WCF Data Services before it, Restier provides simple and straightforward ways to shape queries and intercept submissions before and after they hit the database. And like Web API + OData, you still have the flexibility to add your own custom queries and actions with techniques you're already familiar with.

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 the DataSvcUtil.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.

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. 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.
DataServiceCollection<Customer> trackedCustomers =
    new DataServiceCollection<Customer>(context.Customers.Expand("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"))

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.
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;
' 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.
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()

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.

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<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(Of 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.

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. 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. 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<Customer>(customerQuery);

// Load all pages of the response at once.
while (trackedCustomers.Continuation != null)
{
    trackedCustomers.Load(
        context.Execute<Customer>(trackedCustomers.Continuation.NextLinkUri));
}
' 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

Related objects are loaded in a similar manner. For more information, see How to: Bind Data to Windows Presentation Foundation Elements.

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:

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 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;
    }
}
' 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 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

For more information, see How to: Customize Data Binding Behaviors.

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.

See also