ReflectionSerializer.UpdateObject() & ObservableCollections

Feb 3, 2012 at 5:18 PM

Hey Shawn -

In the AgFx tutorial, you outline the pattern for implementing ObservableCollection properties. Since AgFx will be keeping things in sync for us, we have to clear out and re-add the items to the collection instead of just re-setting the entire collection. For the most part, that has worked well in my app.

However, recently (perhaps since I upgraded to the code in the latest changeset), I have run into an issue. When CacheEntry.UpdateFrom() is triggered, it calls ReflectionSerializer.UpdateObject(), which eventually calls into my ObservableCollection's setter. In that setter, I check if the backing field is not null, and if not, clear the contents and re-add them (just copy/pasted from your example).

The problem arises when the 'value' being passed to the setter is already equal to the property. In other words, when I call .Clear(), it not only clears my backing field, but also clears 'value' since they are the same object. Then when I tried to do the foreach loop to re-add the items, there are no items to add.

For now, I have added a check to my setter that just checks if(myprop != value) before proceeding. I tried to look through the latest changesets to see if I could identify when the issue arose, but didnt see anything that looked like it might be the cause.

Not sure if you simply want to update your example setter implementation or if you the ReflectionSerializer should just do the equality check and no-op if the value and source are the same object?

Thanks,

 - brian

Feb 3, 2012 at 5:59 PM

Specifically, this seems to happen when I called DataManager.Save() and my LoadContext is a static object (ie: my MainViewModel doesnt have any per-request parameters, so I just have a single shared LoadContext that I use for all operations related to that model).

Coordinator
Feb 13, 2012 at 5:16 PM

Hi Brian -

Sorry for the slow reply here, caught me out of town.

OK, I'm having trouble visualizing what you've got going on here.

The main reason for clearing the collection and re-adding to it is because that's the easy way that objects bound to that collection will refresh.  If you replace the collection, you can "zombie" the old collection and you won't get updates.  Now, you can probably get around this by firing a PropertyChange on the collection property itself.

From your description what I hear you saying is that the same value is being pushed into the object that was already there?  Are the items in the collection already also the same?  In other words, do you want a no-op in this case?

So what you'er asking for in ReflectionSerializer is ReferenceEquals check?

Shawn

Feb 13, 2012 at 7:00 PM

Below is your sample code for setting ObservableCollection properties with my comments added. I totally understand why repopulating the same object is necessary; however, I am not familiar enough with the inner working of AgFx to know what will trigger the ReflectionSerializer.UpdateObject() method to be called or why it might be called with the same object that already exists. So, yes, in short, a simple ReferenceEquals check in the ReflectionSerializer would be great (and shouldnt hurt anything since the objects/properties dont need to be updated if they are the same reference anyway). As a temporary workaround, I am just having to do the equality check in my ObservableCollection property setters instead.

private ObservableCollection<WeatherPeriod> _wp = new ObservableCollection<WeatherPeriod>();
public ObservableCollection<WeatherPeriod> WeatherPeriods {
    get {
        return _wp;
    }
    set {
        if (value == null) throw new ArgumentNullException();
        if (_wp != null) {
            _wp.Clear();  // <<< if _wp == value already, then value gets cleared as well

            // since value is empty, there is nothing to iterate over, so _wp does not get repopulated
            foreach (var wp in value) {
               _wp.Add(wp);
            }
           
        }
      
        RaisePropertyChanged("WeatherPeriods");
    }
}

Coordinator
Feb 15, 2012 at 4:07 AM

Ah, okay, I see the problem there in Save.  It looks like you're doing a Save with an instance that's come out of a Load operation?  Basically, you're saving an object who's the current instance in the CacheEntry.  AgFx doesn't know this, so it pushes it into the cache, then updates the currently held instance (from itself).

Can you try the following fix?

CacheEntry.cs, in UpdateFrom:

 

  private void UpdateFrom(ValueLoader loader, object source)
        {
            Version++;

            // if no one is holding the value, don't bother updating.
            //
            if (!CheckIfAnyoneCares())
            {
                return;
            }

            int version = Version;

            object value = ValueInternal;
///// START DIFF
	    if (Object.ReferenceEquals(value, source)) {
		return;
	    }
/// END DIFF


Thanks,

Shawn