Notifying the UI when Entity References Change in Lookup Comboboxes

Last week I wrote about how to data bind WPF lookup comboboxes to entities returned from the Entity Framework. I described that the key to this type of binding is setting the SelectedItem to the object reference itself on the navigation property instead of setting SelectedValue and SelectedValuePath as in the case when you have foreign key scalar properties like LINQ to SQL classes or DataTables.

However, depending on your UI, you may need a notification to fire when the entity reference changes. By default this doesn’t happen with entities generated by the EF designer. Only scalar properties raise change notifications. For instance, going back to our Customer (1)—(*) Order example, the Order entity has a reference to its Customer parent as specified by the navigation property:

In the database there is a foreign key relationship on CustomerID and that is inferred here by EF. If you look at the Order class that is generated you will see only change notifications raised on the scalar properties, not the navigation properties. For instance, if we take a look at a scalar property that is generated you will see the change notification partial methods generated as well:

Partial Public Class Order
    Inherits Global.System.Data.Objects.DataClasses.EntityObject
.
.
. Public Property OrderID() As Integer Get Return Me._OrderID End Get Set Me.OnOrderIDChanging(value) Me.ReportPropertyChanging("OrderID") Me._OrderID = StructuralObject.SetValidValue(value) Me.ReportPropertyChanged("OrderID") Me.OnOrderIDChanged End Set End Property Private _OrderID As Integer
Partial Private Sub
OnOrderIDChanging(ByVal value As Integer) End Sub
Partial Private Sub
OnOrderIDChanged() End Sub
.
.
.

EF entities that are generated by the designer inherit from EntityObject that in turn inherits from StructuralObject that implements  INotifyPropertyChanged. This interface is necessary for notifying the UI (WPF and Winforms) that data bound controls should refresh their value. So say you programmatically change a scalar property then any controls bound to that property will be refreshed with the new value automatically. Or in many cases you have a UI with multiple controls bound to the same property. If the user makes a change to one control, the rest update automatically.

However this notification isn’t generated on entity references. Which means that if you have a lookup combobox set up like I described in last week’s post and also have another control bound to the same Customer navigation property, then it won’t refresh properly.

For instance, say we have an Order form with a combobox set up like before, where the SelectedItem is bound to the Customer property (SelectedItem="{Binding Path=Customer}"), but we also have a listbox that shows OrderDate, Customer.LastName, Customer.FirstName:

image

<ListBox Grid.Row="1" Name="ListBox1" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True">
<ItemsControl.ItemTemplate>
    <DataTemplate>
        <StackPanel Orientation="Horizontal">
            <TextBlock Width="60" Text="{Binding Path=OrderDate, StringFormat='d'}" />
            <TextBlock Text="{Binding Path=Customer.LastName}" />
            <TextBlock Width="5">, </TextBlock>
            <TextBlock Text="{Binding Path=Customer.FirstName}" />
        </StackPanel>
    </DataTemplate>
</ItemsControl.ItemTemplate>

If the user changes the OrderDate then that change will automatically be reflected in the listbox. But if the user changes the Customer in the dropdown combobox then it will NOT update the listbox because a change notification is not raised on Customer. What’s also interesting is if you look at that part of the generated Order entity then you will actually see two properties, one we expect called Customer and one called CustomerReference:

.
.
.
Public Property
Customer() As Customer Get Return CType(Me, IEntityWithRelationships).RelationshipManager. _ GetRelatedReference(Of Customer)("OMSModel.FK_Orders_Customer", "Customer").Value End Get Set(ByVal value As Customer) CType(Me, IEntityWithRelationships).RelationshipManager. _ GetRelatedReference(Of Customer)("OMSModel.FK_Orders_Customer", "Customer").Value = value End Set End Property
.
.
. Public Property
CustomerReference() As EntityReference(Of Customer) Get Return CType(Me, IEntityWithRelationships).RelationshipManager. _ GetRelatedReference(Of Customer)("OMSModel.FK_Orders_Customer", "Customer") End Get Set(ByVal value As EntityReference(Of Customer)) If (Not (value) Is Nothing) Then CType(Me, IEntityWithRelationships).RelationshipManager. _ InitializeRelatedReference(Of Customer)("OMSModel.FK_Orders_Customer", "Customer", value) End If End Set End Property

The Customer property is a navigation property to the parent Customer entity itself as we expect. The CustomerReference is an EntityReference class. This class describes the relationship between the Order and Customer. It also defines an event called AssociationChanged that you can handle to notify the UI properly when the reference changes. When you change the reference this event will fire twice, first to remove the old reference and then again to add the new one. You can easily extend the Order partial class by creating another Partial Class declaration for Order in the same namespace (which is automatically imported in VB) and then calling the appropriate property change notifications:

Imports System.ComponentModel

Partial Public Class Order
    Sub New()
        MyBase.New()
        AddHandler Me.CustomerReference.AssociationChanged, AddressOf Customer_AssociationChanged
    End Sub

    Private Sub Customer_AssociationChanged(ByVal sender As Object, _
                                            ByVal e As CollectionChangeEventArgs)
        If e.Action = CollectionChangeAction.Remove Then
            OnPropertyChanging("Customer")
        Else
            OnPropertyChanged("Customer")
        End If
    End Sub
End Class

So now we can change the Customer in the dropdown and the UI will be notified properly. Sweet. For more information on Entity Framework and data binding see this topic in the MSDN library.

Enjoy!