Binding Data to Controls (ADO.NET Data Services)

Note

This topic describes new functionality in ADO.NET Data Services that is available as an update to the .NET Framework version 3.5 Service Pack 1. You can download and install the update from the Microsoft Download Center.

With ADO.NET Data Services, you can bind controls such as the ComboBox and ListView controls to an instance of the DataServiceCollection class, which inherits from the ObservableCollection class. This class represents a dynamic data collection that provides notifications when items get added or removed. When you use an instance of DataServiceCollection for data binding, the ADO.NET 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 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 must also implement the INotifyPropertyChanged interface to alert the DataServiceCollection when properties of objects in the binding collection have changed.

Note

When you use 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 (ADO.NET Data Services).

Creating the Binding Collection

Create a new instance of the DataServiceCollection class by calling one of the class constructor methods with a supplied DataServiceContext instance and optionally a DataServiceQuery or LINQ query that, when it is executed, returns an IEnumerable instance. This IEnumerable provides the source of objects for the binding collection. 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 based on a supplied DataServiceContext and a DataServiceQuery that returns all customers with related orders:

' Create a new collection that contains all customers and related orders.
Dim trackedCustomers As DataServiceCollection(Of Customers) = _
        New DataServiceCollection(Of Customers)(context.Customers.Expand("Orders"))
// Create a new collection that contains all customers and related orders.
DataServiceCollection<Customers> trackedCustomers = 
    new DataServiceCollection<Customers>(context.Customers.Expand("Orders"));

Binding Data to Windows Presentation Foundation Elements

Because the DataServiceCollection class inherits from the ObservableCollection class, you can bind objects to an element, or control, in a Windows Presentation Foundation (WPF) application as you would when using the ObservableCollection 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 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 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 Customers)(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 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
// 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<Customers>(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<Customers>(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}" 
                    Header="Order ID" Width="50"/>
                <GridViewColumn DisplayMemberBinding="{Binding Path=OrderDate}" 
                    Header="Order Date" Width="50"/>
                <GridViewColumn DisplayMemberBinding="{Binding Path=Freight}" 
                    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 (ADO.NET 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 DataSvcUtil.exe tool to generate the client data service classes, the navigation property returns an instance of DataServiceCollection. 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 to the root data element. The order DataGrid is then bound to the Orders DataServiceCollection 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 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 are not reflected in the bound control.

The following example binds an DataServiceCollection to a ComboBox control:

' Create a new collection for binding based on the LINQ query.
trackedCustomers = New DataServiceCollection(Of Customers)(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<Customers>(customerQuery);

//Bind the Customers combobox to the collection.
customersComboBox.DisplayMember = "CustomerID";
customersComboBox.DataSource = trackedCustomers;

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 (ADO.NET 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 (ADO.NET Data Services). In this case, you must explicitly load pages by calling the Load(UTP) method on the DataServiceCollection 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 Customers)(customerQuery)

    ' Load all pages of the response at once.
While trackedCustomers.Continuation IsNot Nothing
    trackedCustomers.Load( _
            context.Execute(Of Customers)(trackedCustomers.Continuation.NextLinkUri))
End While
// Create a new collection for binding based on the LINQ query.
trackedCustomers = new DataServiceCollection<Customers>(customerQuery);

// Load all pages of the response at once.
while (trackedCustomers.Continuation != null)
{
    trackedCustomers.Load(
        context.Execute<Customers>(trackedCustomers.Continuation.NextLinkUri));
}

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

Customizing Data Binding Behaviors

The DataServiceCollection 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, 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 delegate accepts an EntityChangedParams object and returns a Boolean value that indicates whether the default behavior, to call UpdateObject(Object) 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 delegate accepts an EntityCollectionChangedParams object and returns a Boolean value that indicates whether the default behavior, to call AddObject(String, Object) for an Add() action or DeleteObject(Object) for a Remove() action on the DataServiceContext, should still occur.

Note

ADO.NET 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(Object, String, Object) and DeleteObject(Object) 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(Orders) Then

            ' Get the context and object from the supplied parameter.
            Dim context = entityCollectionChangedinfo.Context
            Dim deletedOrder As Orders = _
            CType(entityCollectionChangedinfo.TargetEntity, Orders)

            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_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 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(Orders))
        {
            // Get the context and object from the supplied parameter.
            DataServiceContext context = entityCollectionChangedinfo.Context;
            Orders deletedOrder = entityCollectionChangedinfo.TargetEntity as Orders;

            if (deletedOrder.Order_Details.Count == 0)
            {
                // Load the related OrderDetails.
                context.LoadProperty(deletedOrder, "Order_Details");
            }

            // Delete the order and its related items;
            foreach (Order_Details item in deletedOrder.Order_Details)
            {
                //context.DeleteLink(deletedOrder, "Order_Details", item);
                context.DeleteObject(item);
            }

            // Delete the order and then return false 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 (ADO.NET Data Services).

The default behavior when an object is removed from a DataServiceCollection by using the Remove(UTP) 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, the objects themselves must implement the INotifyPropertyChanged interface. Data service client classes that are generated when you use 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(String, Object) - when a new object is added to the collection.

  • DeleteObject(Object) - when an object is removed from the collection.

  • UpdateObject(Object) - when a property is changed on an object in the collection.

  • AddLink(Object, String, Object) - when an object is added to a collection of related object.

  • SetLink(Object, String, Object) - when an object is added to a collection of related objects.

For more information, see Updating the Data Service (ADO.NET Data Services).

See Also

Other Resources

How to: Manually Generate Client Data Service Classes (ADO.NET Data Services)

How to: Add a Data Service Reference (ADO.NET Data Services)