次の方法で共有


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!

Comments

  • Anonymous
    May 04, 2009
    PingBack from http://microsoft-sharepoint.simplynetdev.com/notifying-the-ui-when-entity-references-change-in-lookup-comboboxes/

  • Anonymous
    May 09, 2009
    I have the same problem but I'm using Linq2Sql; is there anything similar to AssociationChanged in Linq2Sql? Thank you.

  • Anonymous
    May 10, 2009
    I have the same problem using Linq2Sql; is there anything similar to AssociationChanged in Linq2Sql? Thank you.

  • Anonymous
    May 11, 2009
    Hi Daniele, In L2S you can use the technique as when working with DataTables (http://msdn.microsoft.com/en-us/vbasic/cc788742.aspx), bind to the foreign key values instead of the association. <ComboBox IsEditable="False"      DisplayMemberPath="LastName"      SelectedValuePath="CustomerID"      SelectedValue="{Binding Path=CustomerID}" /> HTH, -B

  • Anonymous
    May 12, 2009
    Thank you Beth. To be honest my problem is different; I was simplyfing it because AssociationChanged could solve my trouble. In the truth I need to recalculate some temporary field when I add/remove an associated object. I tried these:

  • call my method in the setter of the association (in the designer); it works but every time I edit the db schema the designer erase my code;
  • implements OnIdmyobjectChanged() in the partial class but it isn't raised when I add/remove myobject to/from the association. Am I missing something?
  • Anonymous
    May 15, 2009
    I’ve been writing a lot about building WPF business applications with Entity Framework using Visual Studio

  • Anonymous
    April 19, 2010
    I love you! I have to translate in C# code... public Order() : base()        {           this.CustomerReference.AssociationChanged +=new CollectionChangeEventHandler(this.Customer_AssociationChanged);                  }        private void Customer_AssociationChanged(object sender,  CollectionChangeEventArgs e ) {            if (e.Action.Equals(CollectionChangeAction.Remove)) {                OnPropertyChanging("Customer");            } else {                OnPropertyChanged("Customer");            }        }

  • Anonymous
    June 29, 2010
    Is there a way to automatically update a Lookup Combo when the underlying data changes? I have a combo wired up to a CustomerCollection as you have written in your note above, where Customer is a Linq to SQL entity and CustomerCollectin derives from ObservableCollection. I also have a DataGrid showing Customers. They are both wired up to the same CustomerCollection via different CollectionViews. When I insert or remove a Customer via the DataGrid CollectionView the combo updates automatically. But I don't know how to make this happen for changes. I am doing the changes in an independent Customer object because the requirement is that the DataGrid should not reflect the changes until after they have been saved to the database. So the problem is first to get the changes into the DataGrid CollectionView, and in such a way that the combo updates automatically. I tried deleting the corresponding Customer object (the one with the same CutomerID as the independent object where I do the changes and save them to the database) from the view and re-inserting it, and it all works as required EXCEPT of course where that item was selected in the combo at the time, in which case it then displays the wrong Customer name.

  • Anonymous
    June 30, 2010
    Hi Catherine, As long as the objects in the collection implement INotifyPropertyChanged then changes to any property on these objects will be reflected in the UI, including the combobox. HTH, -B

  • Anonymous
    February 14, 2012
    Having to agree with Ray (on both statements) Beth, very good articles.  Thank you.  Rarely do I read articles written for VB, but this had some stuff i needed to learn. Since you write well...can you include C# in future articles as well?  (Better yet, JUST C#!!)

  • Anonymous
    September 04, 2012
    The problem with this is if you simply set a related entity to null then the UI doesn't update since only the CollectionChangeAction.Remove condition occurs and OnPropertyChanged is never called. Is there a smarter version of this event handler that works with only removals?

  • Anonymous
    December 10, 2014
    Tank you, it was very helpfull for me.