none
Notify Property Changed on References

    Question

  • When I have an entity with a one 1:N reference, the ef creates two properties on that entity.  For example, I have a User entity and a Role entity. Each user entity can point to one Role entity (and each role can point to mulitple users).

     

    The EF creates two properties on my user entity, similar to this:

     

    MyUser.Role

    MyUser.RoleReference

     

    If I set the Role property, the object does not broadcast notification that this changed.  All the other scaler properties do.  The other properties contain code such as this:

     

    Code Snippet

    Me.OnPropertyNameChanging(value)

    Me.ReportPropertyChanging("PropertyName")

    Me._PropertyName = Global.System.Data.Objects.DataClasses.StructuralObject.SetValidValue(value)

    Me.ReportPropertyChanged("PropertyName")

    Me.OnPropertyNameChanged

     

     

    But the code for a reference property is only one line, like this:

     

    Code Snippet

    CType(Me,Global.System.Data.Objects.DataClasses.IEntityWithRelationships).RelationshipManager.GetRelatedReference(Of Role)("MyModel.FK_Users_Roles", "Roles").Value = value

     

     

    For some reason, you can see in the second snippet, MS has decided not to call the onpropertychanged methods.  Why is this?

     

    Is there a workaround I can do here to trigger this call without making the Setter private and calling a custom "SetRole" method?

     

    Thanks in advance for any help.

    Monday, November 10, 2008 8:19 PM

Answers

  •  

    Yes, here is what I found:

     

    EntityReferences and EntityCollections are treated differently than scalar and complex value properties.

    The EntityReference and EntityCollection classes both expose an “AssociationChanged” event. You can listen to this event to see when the value in the related end changes. One thing you can do is to register an AssociationChanged event handler and have it call the INotifyPropertyChanged event. To do this, you can take advantage of the fact that your code generated entities are partial classes and extend them with the registration of this handler, you can take the workaround below as a reference:

     

    (Suppose class names are “User” and “Role”):

     

    Code Snippet

        public partial class User

        {

            private void RoleChanged(Object sender, CollectionChangeEventArgs e)

            {

                // Call the protected OnPropertyChanged method

                // which will fire the INotifyPropertyChanged.PropertyChanged event

                OnPropertyChanged("Role");

            }

     

            // Create a default constructor that registers your event handler

            public User()

            {

                this.RoleReference.AssociationChanged += RoleChanged;

            }

        }

     

     

    This works perfect for me, but I would really like to see MS implement INotifyPropertyChanged in the next version.

     

    Hope this helps.  There is more info on my blog: http://blog.nicktown.info/2008/12/02/entity-framework-doesnt-support-inotifypropertychanged-on-some-properties.aspx

     

    Nicholas

    Tuesday, December 02, 2008 2:11 PM
  • This is not a problem. I suggest you add a Breakpoint to you application within the AssociationChanged event handler so that you can see what actually happens when the property is changed.

    After you set your breakpoint, you will notice that the event fires one or two times everytime the reference is changed. More importantly, you will notice that there is a parameter passed to you of type "System.ComponentModel.CollectionChangeEventArgs". This is what you need to accomplish your task.

    In the event args, there is a property "Action", this will tell you if an element was Added or Removed. There is also an Element property. This is actually a reference to the entity added or removed.

    For fun, try this out. Assume you create a new user and the Role property is Null.

    1. Set the Role property to some role. You will see that AssociationChanged is fired one time with the Added action.

    2. Now set the Role property to a different role. This time, you will see that AssociationChanged is fired two times! The first time is with the Removed action and the second is with the Added action.

    3. Now set the Role property to Null. Here the AssociationChanged is only fired once, with the Removed action. So, to accomplish your task, you have a simple if within your AssociationChanged handler. Here is a quick code snippet in VB.

    So, to accomplish your task, you have a simple if within your AssociationChanged handler.  Here is a quick code snippet in VB.

    Code Snippet:

    Partial Public Class User
    
    Public Sub New()
    
    AddHandler Me.RoleReference.AssociationChanged, AddressOf RoleChanged
    
    End Sub
    
     
    
    Public Sub RoleChanged(ByVal sender As Object, ByVal e As System.ComponentModel.CollectionChangeEventArgs)
    
     
    
    If e.Action = System.ComponentModel.CollectionChangeAction.Add Then
    
    AddHandler DirectCast(e.Element, Role).PropertyChanged, AddressOf SomeMethodToCallMyPropertyChanged
    ElseIf e.Action = System.ComponentModel.CollectionChangeAction.Remove Then
    
    RemoveHandler DirectCast(e.Element, Role).PropertyChanged, AddressOf SomeMethodToCallMyPropertyChanged
    End If
    
    End Sub
    
     
    
    Public Sub SomeMethodToCallMyPropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs)
    
    OnPropertyChanged(e.PropertyName)
    
    End Sub
    
    End Class

    That should be all there is too it.
    Wednesday, December 03, 2008 6:33 PM

All replies

  •  

    I am not sure if this fully answers the question but i think one of the resons it is not there is because there is an assocaiton changed event that gets fired when a relationship between object changes. for e.g if i have customer and it has multiple addresses i can access teh assocaition change event form both sides like this

     

    customer.Addresses.AssociationChanged += new System.ComponentModel.CollectionChangeEventHandler(Addresses_AssociationChanged);

    var address = new Address();

    address.CustomerReference.AssociationChanged += new System.ComponentModel.CollectionChangeEventHandler(CustomerReference_AssociationChanged);

     

     

    Zeeshan HIrani

    Monday, November 10, 2008 8:40 PM
  • Thanks for the quick reply.  I will look into this event.

     

    One of my annoyances is that in the example I gave above, I have a binding that shows the role assigned, but because the notify changed is not triggered on the reference, the binding connected to User.Role.Name does not update on the screen until they move off the user record and back on.

     

    Seeing as the event you mention is part of the System.ComponentModel, it is strange to me that this is not getting trapped by the property bindings.  Although, with this being said, in the case you gave, you actually have the opposite scenario... you are looking at a cusotmer with many addresses.  In my case I am looking at a user with only one role.  So it is possible the event you are referring to only occurs in the event that there is a N:1, not 1:N reference.

     

    As an FYI, I am using WPF.

    Monday, November 10, 2008 8:48 PM
  •  

    Any one else have any ideas?
    Thursday, November 13, 2008 8:07 PM
  • Do you find anyway to resolve the problem, I'm facing to the same issue ?

    Changing a reference (using a combobox) will not be reflected in the list above using binding on thei referenced object.

    Also using WPF.

    Thanks
    Didier


    Tuesday, December 02, 2008 8:38 AM
  •  

    Yes, here is what I found:

     

    EntityReferences and EntityCollections are treated differently than scalar and complex value properties.

    The EntityReference and EntityCollection classes both expose an “AssociationChanged” event. You can listen to this event to see when the value in the related end changes. One thing you can do is to register an AssociationChanged event handler and have it call the INotifyPropertyChanged event. To do this, you can take advantage of the fact that your code generated entities are partial classes and extend them with the registration of this handler, you can take the workaround below as a reference:

     

    (Suppose class names are “User” and “Role”):

     

    Code Snippet

        public partial class User

        {

            private void RoleChanged(Object sender, CollectionChangeEventArgs e)

            {

                // Call the protected OnPropertyChanged method

                // which will fire the INotifyPropertyChanged.PropertyChanged event

                OnPropertyChanged("Role");

            }

     

            // Create a default constructor that registers your event handler

            public User()

            {

                this.RoleReference.AssociationChanged += RoleChanged;

            }

        }

     

     

    This works perfect for me, but I would really like to see MS implement INotifyPropertyChanged in the next version.

     

    Hope this helps.  There is more info on my blog: http://blog.nicktown.info/2008/12/02/entity-framework-doesnt-support-inotifypropertychanged-on-some-properties.aspx

     

    Nicholas

    Tuesday, December 02, 2008 2:11 PM
  • Hi,

     

    so far so good. I came to the same approach. Everything is fine until I clean the reference by setting the property to null (Role = null). As a result the Role is null (ok) but the AssociationChanged event is not fired.

    How do I get a notification for this case? Is there a better way to clean a reference in the business logic (means without internal reference handling)?

     

    And there is another issue. If I want to go on in this way and implement a deeper notification about Role property changes, I can subscribe the Role.PropertyChanged event and forward the changes via OnPropertyChanged.

    If another Role is assigned I get the AssociationChanged event with the new entity. So I can register to the event of the new instance. But the removed instance is no longer available. So how can I unsubscribe the event of the removed Role? Do I have to backup the reference?

     

    Many thanks in advance.

    hl

    Wednesday, December 03, 2008 12:09 PM
  •  hlange wrote:

    so far so good. I came to the same approach. Everything is fine until I clean the reference by setting the property to null (Role = null). As a result the Role is null (ok) but the AssociationChanged event is not fired.

    How do I get a notification for this case? Is there a better way to clean a reference in the business logic (means without internal reference handling)?

     

    First off, dealing with your first issue.  I have tested this case and I seem to have no issues as the AssociationChanged event is fired.  When fired, the CollectionChangedEventArgs returns Action = Removed.

    Wednesday, December 03, 2008 4:30 PM
  •  hlange wrote:

    And there is another issue. If I want to go on in this way and implement a deeper notification about Role property changes, I can subscribe the Role.PropertyChanged event and forward the changes via OnPropertyChanged.

    If another Role is assigned I get the AssociationChanged event with the new entity. So I can register to the event of the new instance. But the removed instance is no longer available. So how can I unsubscribe the event of the removed Role? Do I have to backup the reference?

     

     

    It may be that I am not understanding your situation precisely.  You may need to give a quick code sample.  In the end, you are adding the eventhandler to the user entity, not the role entity.  This event handler will ensure that the Role property notifies you if it changes.  If you want to know if a property of Role changes, you will need to ensure the properties in the Role object will broadcast the PropertyChangedEvent.

     

    Correct me if I am wrong, but you want to bind to the User object and determine if a property of its Role has changed.  In this case, you could use binding to do so.

     

    For example, in a WPF program, you could set the path to be "Role.Name" (if you want notification on the name of the role changing).

     

    Let me know if I can clarify.

    Wednesday, December 03, 2008 4:39 PM
  • Hi Nicholas,

     

    due to your hint I check it again and the event is actually fired, but only for the previously referenced element. Originally I expected to get the event on the other side. But ok, this will help.

     

    Thanks again for the quick response.

    Wednesday, December 03, 2008 5:22 PM
  • OK, I'll try to explain it a bit more in detail:

    I have a View with a ViewModel which is used for the data binding and decouples View and business model. The ViewModel wraps the business model e.g. a Person and subscribes whose PropertyChanged event. To reduce the complexity of the notification and its management the top level entity shall notify about all changes of itself and related entities. Therefore it has to subscribe the PropertyChanged event of the referenced Role and forwards notifications with OnPropertyChanged().

     

    If another Role is assigned to the Person the old subscription has to be removed. The question is now, how to handle this when the AssociationChanged event only provides the new instance?

    Wednesday, December 03, 2008 6:09 PM
  • This is not a problem. I suggest you add a Breakpoint to you application within the AssociationChanged event handler so that you can see what actually happens when the property is changed.

    After you set your breakpoint, you will notice that the event fires one or two times everytime the reference is changed. More importantly, you will notice that there is a parameter passed to you of type "System.ComponentModel.CollectionChangeEventArgs". This is what you need to accomplish your task.

    In the event args, there is a property "Action", this will tell you if an element was Added or Removed. There is also an Element property. This is actually a reference to the entity added or removed.

    For fun, try this out. Assume you create a new user and the Role property is Null.

    1. Set the Role property to some role. You will see that AssociationChanged is fired one time with the Added action.

    2. Now set the Role property to a different role. This time, you will see that AssociationChanged is fired two times! The first time is with the Removed action and the second is with the Added action.

    3. Now set the Role property to Null. Here the AssociationChanged is only fired once, with the Removed action. So, to accomplish your task, you have a simple if within your AssociationChanged handler. Here is a quick code snippet in VB.

    So, to accomplish your task, you have a simple if within your AssociationChanged handler.  Here is a quick code snippet in VB.

    Code Snippet:

    Partial Public Class User
    
    Public Sub New()
    
    AddHandler Me.RoleReference.AssociationChanged, AddressOf RoleChanged
    
    End Sub
    
     
    
    Public Sub RoleChanged(ByVal sender As Object, ByVal e As System.ComponentModel.CollectionChangeEventArgs)
    
     
    
    If e.Action = System.ComponentModel.CollectionChangeAction.Add Then
    
    AddHandler DirectCast(e.Element, Role).PropertyChanged, AddressOf SomeMethodToCallMyPropertyChanged
    ElseIf e.Action = System.ComponentModel.CollectionChangeAction.Remove Then
    
    RemoveHandler DirectCast(e.Element, Role).PropertyChanged, AddressOf SomeMethodToCallMyPropertyChanged
    End If
    
    End Sub
    
     
    
    Public Sub SomeMethodToCallMyPropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs)
    
    OnPropertyChanged(e.PropertyName)
    
    End Sub
    
    End Class

    That should be all there is too it.
    Wednesday, December 03, 2008 6:33 PM
  • Many thanks. Everything is fine. The inital subscription failed...

     

    Thursday, December 04, 2008 10:01 AM