HasChanges not set correctly

Nov 8, 2011 at 7:08 PM

In the sample demo application, let's suppose after adding a trainer to a dog, shouldn't the SelectedDog.HasChanges return true?

This is the line in question:

mainPageModel.SelectedDog.Trainers.Add(mainPageModel.SelectedTrainer);
Coordinator
Nov 9, 2011 at 6:22 AM

Hi Duluca,

That is not how associations in Ria (or in relational databases) work. There is no property (or column) on Dog that changes by adding a trainer to it. In your example there will be a new join table object created of which the two foreign key properties point to the dog and trainer object. This new join table table object is the only change that occurs when associating a trainer to a dog. The HasChanges on Dog therefore returns false.

I hope this helps.

Meirjn

Nov 9, 2011 at 1:44 PM

That makes sense.  I was approaching this from the view point of a data form. Suppose I have SelectedDog as the DataContext of a DataForm. You can bind SelectedDog.Trainers to a list control and enable adding of trainers to this list through a custom control. I already have this portion of it working correctly. Here's the difference though, if this was a one to many relationship, then the Ria generated code marks SelectedDog as HasChanges, when you add/remove a Trainer. At a lower level, the Dog table isn't changed, but at the higher Entity level (at least conceptually) that entity did indeed change. Because now it has an extra Trainer associated with it.

What are you thoughts?

Coordinator
Nov 9, 2011 at 2:30 PM

I've developed EntityGraph (http://riaservicescontrib.codeplex.com/wikipage?title=EntityGraphs) for things like that. EntityGraph enables you to define a graph of related entities (e.g., the dogs and trainers) and to handle such graphs as a unit rather than as a collection of individual entities. For instance, there is a HasChanges property on an entity graph that is set to true if any of the entities in the graph has pending changes and false otherwise. This may help you solve your problem. You can do much more with EntityGraph  (e.g., cross-entity validation). Have a look at the web-site for more info.

EntityGraph is available as a Nuget package (http://nuget.org/List/Packages/EntityGraph).

Nov 9, 2011 at 4:06 PM

Great! Thanks for all the great work. I had seen the Entity Graph before and I'm aware of the benefits. I guess I'll bite the bullet and implement it.

Do you have any experience with graph as a datacontext for a DataForm? Does it just work or requires some/a lot tricks/workarounds?

Coordinator
Nov 10, 2011 at 1:19 PM

No, I haven't used a graph as a datacontext, but I don't expect any problems in doing so. So I don't think any tricks/workarounds are needed.

Having said that, using a graph as datacontext is not really MVVM-style... It is better to expose the graph via a property on your view model.

Nov 10, 2011 at 2:11 PM

Ok, thanks. I'll let you know how it goes. Yeah, the MVVM design goes without saying.

Nov 10, 2011 at 6:43 PM
I have added the EntityGraph to your M2M sample code and I've been trying to understand how these two would work together.

public
partial class Dog { private static EntityGraphShape _shape = new EntityGraphShape() .Edge<Dog, Trainer>(d => d.Trainers) .Edge<Dog, DogTrainer>(d => d.DogTrainerToTrainerSet); public static EntityGraphShape Shape { get { return _shape; } } }

Below is how I modified AddTrainerCommand's Execute Method:
public override void Execute(object parameter)
{
    mainPageModel.SelectedDog.Trainers.Add(mainPageModel.SelectedTrainer);
    var gr = mainPageModel.SelectedDog.EntityGraph(Dog.Shape);
    mainPageModel.AutoSaveChanges();
    CanExecute(parameter);
}

After gr is defined, below is what I've observed:
- gr.HasChanges => false //Unexpected
- gr.GetChanges() => {Added = 1, Modified = 0, Removed = 0} //Expected

If gr is defined before the trainer is added, then I get the below error:
System.InvalidOperationException was unhandled by user code
  Message=Sequence contains no elements
  StackTrace:
       at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source)
       at RiaServicesContrib.EntityGraph`1.Validator_CollectionChanged(Object sender, NotifyCollectionChangedEventArgs args)
       at RiaServicesContrib.EntityGraph`1.collection_CollectionChanged(Object sender, NotifyCollectionChangedEventArgs e)
       at System.Collections.Specialized.NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e)
       at M2M4RiaDemo.Web.Model.M2M4Ria.EntityCollection`2.<.ctor>b__2(Object sender, NotifyCollectionChangedEventArgs e)
       at System.Collections.Specialized.NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e)
       at System.ServiceModel.DomainServices.Client.EntityCollection`1.RaiseCollectionChangedNotification(NotifyCollectionChangedEventArgs args)
       at System.ServiceModel.DomainServices.Client.EntityCollection`1.Add(TEntity entity)
       at M2M4RiaDemo.Web.Model.DogTrainer.set_Dog(Dog value)
       at M2M4RiaDemo.Web.Model.DogTrainer.AttachTrainerToDog(DogTrainer r, Dog dog, Trainer trainer)
       at M2M4RiaDemo.Web.Model.Dog.AddDogTrainer(Trainer trainer)
       at M2M4RiaDemo.Web.Model.M2M4Ria.EntityCollection`2.Add(TEntity entity)
       at M2M4RiaDemo.Models.AddTrainerCommand.Execute(Object parameter)

I don't know if this is the intended behavior, but from all the samples I have seen, it looks like the graph is suitable to observe what *already* happened, but not really a container/package of all these related entities.

And I was still surprised to see, HasChanges being false. Could this be a bug in M2M/EntityGraph or am I doing something wrong here?

Best
Coordinator
Nov 10, 2011 at 7:04 PM

Thanks for sharing. I'll have to have a closer look at it. I hope I'll have time for that coming weekend.

Coordinator
Nov 11, 2011 at 7:38 PM

Thanks, You found 2 bugs in EntityGraph. The first is a bug in the implementation of HasChanges. It didn't record added/removed entities as changes. The second bug caused the exception.

I've fixed the bugs, committed the changes to riaservicescontrib.codeplex.com and I've uploaded a NuGet package.

Please let me know if it works for you now.

Nov 14, 2011 at 6:17 PM

Nice. I just verified the fixes. Thank you for your prompt release.

So I'm going with a design, as shown below:

    public partial class Dog
    {
        private static EntityGraphShape _shape =
            new EntityGraphShape()
                .Edge<Dog, Trainer>(d => d.Trainers)
                .Edge<Dog, DogTrainer>(d => d.DogTrainerToTrainerSet);

        public static EntityGraphShape Shape { get { return _shape; } }

        EntityGraph _graph;

        public EntityGraph Graph
        {
            get
            {
                if (_graph == null)
                {
                    _graph = new EntityGraph(this, Dog.Shape);
                }
                return _graph;
            }
        }

        public bool HasChanges
        {
            get
            {
                return Graph.HasChanges;
            }
        }
    }

The pros here are no change to my current View (XAML) and ViewModel. It also keeps the changes contained. A con could be a design issue, where conceptually the graph is an external entity owning the Dog, but here it's implemented as a part of Dog.

Do you think this design makes sense?