3

So I'm building my first larger application and I'm using WPF for Windows and stuff and Entity Framework for retrieving, updating and storing data.So far using a pattern similar to the MVVM pattern, I had a couple of issues but was able to resolve them and am quite far into design. Also, I'm using database first approach.

But I have just ran into a brick wall that I should have anticipated. It has to do with nested properties in entities and the way changes to them are handled. Let's explain.

For the purpose of simplicity I will not be using my actual class names. So let's say I have three entities in my EF Model: Department, Manager and PersonalInfo. I modified my *.tt Template file so that all my entities also implement INotifyPropertyChanged interface, but only for their NON NAVIGATION properties since Navigation properties are declared as virtual and WILL be overridden by EF when their date gets set.

So let's say my generated classes look like this:

public partial class Department : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropChange(string property)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }

    public Department() { }

    int _id;
    public int ID { get { return _id; } set { _id = value; OnPropChange("ID"); } }

    int _someproperty;
    public int SomeProperty { get { return _someproperty; } set { _someproperty= value; OnPropChange("SomeProperty"); } }

    int _managerid;
    public int ManagerID { get { return _managerid; } set { _managerid = value; OnPropChange("ManagerID"); } }
    public virtual Manager Manager { get; set; }
}

public partial class Manager : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropChange(string property)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }
    public Manager() { }

    int _id;
    public int ID { get { return _id; } set { _id = value; OnPropChange("ID"); } }

    public virtual PersonalInfo PersonalInfo { get; set; }
}

public partial class PersonalInfo : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropChange(string property)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }

    public PersonalInfo() { }

    int _id;
    public int ID { get { return _id; } set { _id = value; OnPropChange("ID"); } }

    string _firstname;
    public string FirstName { get { return _firstname; } set { _firstname = value; OnPropChange("FirstName"); } }

    string _lastname;
    public string LastName { get { return _lastname; } set { _lastname = value; OnPropChange("LastName"); } }
}

Now this works pretty well if I want to let's say display a list of Departments with their Managers. First I load the data into the EF Context like so

Context.Departments.Include(d => d.Manager.PersonalInfo).Load();
Departments = Context.Deparments.Local;

And than in the XAML I can do:

<DataGrid ItemsSource="{Binding Departments}" SelectedItem="{Binding CurrentDepartment, Mode=TwoWay}">
   <DataGrid.Columns>
       <DataGridTextColumn Binding="{Binding ID}"/>SomeProperty 
       <DataGridTextColumn Binding="{Binding SomeProperty }" Header="Property"/>
       <DataGridTextColumn Binding="{Binding Manager.PersonalInfo.FirstName}" Header="FirstName"/>
       <DataGridTextColumn Binding="{Binding Manager.PersonalInfo.LastName}" Header="LastNameName"/>
   </DataGrid.Columns>
</DataGrid>

And all of this works wonderfully. I can add and remove items with no problems by simply removing them from Context and saving changes. Since entity sets are ObservableCollections any additions or removal from them automatically raises appropriate events which update the datagrid. I can also modify any nonnavigation property of the Department and can refresh the data in CurrentDepartment like so:

 Context.Entry(CurrentDepartment).Refresh();

and it automatically refreshes the data in the datagrid.

Problems start when I change one of the navigation properties. Let's say that I opened a window in which I edited the Department, where I changed the current manager from Bob Bobington to Dave Daveston. When I return to this window calling:

 Context.Entry(CurrentDepartment).Refresh();

It will only refresh non navigation properties, and First and Lastname columns will still say Bob Bobington. But that is Refresh function working as intended. But if I load the correct data into the context like this:

  Context.Entry(CurrentDepartment).Reference(d=>d.Manager);
  Context.Entry(CurrentDepartment.Manager).Reference(m=>m.PersonalInfo);

is still won't change the contents of the first and last name columns since they are still bound to the OLD manager. They will only refresh if the change happens on Bob Bobington instance of PersonalInfo.

I can sort of solve this level of problem by binding the column directly to Manager property, and converting Manager to text either via a ValueConverter or by overriding ToString for Manager. But that won't help since WPF won't ever be notified that Manager property has changed since changes to that property don't raise PropertyChanged event.

Navigation properties can not raise that event since even if I edited the tt template so it generates the code for the navigation property like so:

 Manager _manager;
 public virtual Manager Manager { get{return _manager;}  
      set{
           _manager=value;
           OnPropChange("Manager");
      }
 }

all this code will likely be overridden by the EF framework itself.

Sooo, what is the best thing to do in these cases? Please don't tell me that conventional wisdom is to copy the data from EF Poco classes into your own and use them. :(

UPDATE:

Here goes a potentially stupid solution for this problem. But it works.

ObservableCollection<Department> tempd = Departments;
Department temp = CurrentDepartment;
Departments = null;
CurrentDepartment = null;

Context.Entry(temp).Refresh();
Context.Entry(temp).Reference(d=>d.Manager).Load();
Context.Entry(temp.Manager).Reference(m=>m.PersonalInfo).Load();

Departments = tempd;
CurrentDepartment = temp;

As you can clearly see the key is in forcing the DataGrid to rebind itself from scratch. This way it will use no shortcuts and will rebind itself properly. BUT this method is quite silly. I shiver at the thought of having to do this to datagrids with hundreds of rows.

So I'm still waiting for a proper solution, but I'll be continuing onwards using this. Something is better than nothing.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
4th Dimension
  • 65
  • 2
  • 11
  • If you modify `ManagerId`, calling `context.ChangeTracker.DetectChanges` should keep the property in sync with `Manager`. – Gert Arnold Mar 30 '14 at 21:04
  • I don't think that will help since from what I understand DetectChanges checks weather the entity has changed since last DB call/sync. It raises no event and it doesn't tell you when something changes in the DB. Also keeping a correct value in manager is not really the issue, the problem is that if the Manager changes it raises no events that WPF can hook into and know when it changed. – 4th Dimension Mar 31 '14 at 13:42
  • No, it doesn't raise events, but it executes *relationship fixup*, an internal process by which EF ensures that foreign keys and references are in synch. It will cause the correct manager to be fetched from the database when you address the refrence. – Gert Arnold Mar 31 '14 at 13:55
  • Ok, but that doesn't solve my problem. I allready know how to fetch a correct Manager from the DB. I use Refresh on the Department which loads the correct managerID from the Database, and than I load the manager by using Reference().Load(). From what I understand of the DetectChanges, it will detect only the changes on the in memory entities. So if say I change managerID in another window in another context, this Context's DetectChanges WILL NOT detect that managerID changed in the DB. And as my problem lies in notifying WPF databinding that navigation prop has changed. – 4th Dimension Apr 01 '14 at 09:35
  • Possible duplicate of http://stackoverflow.com/questions/6707215/ef-entityobject-not-updating-databindings-of-relationships – chuwik Aug 12 '14 at 16:28
  • kinda late but if you want a quick fix just call `OnPropChange("Manager");` before return on `ManagerID `get – andySF Mar 31 '15 at 20:28
  • Huh. That is a good workaround, buuut. Here I'm working with aoutogenerated code. This code will be regenerated every time I change the EF model. So I can not manually add that property changed. I wonder can program the *.tt files to check if a property is participating in a relation, and from that learn the navigation property. I will have to investigate this. So thanks. Unless you know how should I change the *.tt files? – 4th Dimension Apr 01 '15 at 21:57

1 Answers1

1

Well, conventional wisdom is to copy the data across to another POCO, or at least make your ViewModel class peek through to an underlying Model class. You have combined your Model and ViewModel classes such that Model-based constraints (virtual methods required by your ORM) are interfering with your ViewModel-based constraints (to allow databinding, you must be able to raise events from property setters).

If your Model and ViewModel were properly separated (Separation of Concerns) then you could have your virtual methods and database-required fields on your Model (a DB persistable object) and your purely View-based functions (PropertyChanged events) on your ViewModel. Your DB code should never care about your PropertyChanged events anyway.

You can make it easier by making the ViewModel a look-through class so every property getter-setter looks like:

public string PropertyThing
{
    get { return _myModel.PropertyThing; }
    set { _myModel.PropertyThing = value; PropChanged("PropertyThing"); }
}

If you're already doing code generation this shouldn't be a major chore.

Alternatively, you could duplicate all the values with something like AutoMapper to separate out your Model and ViewModel to separate classes.

It's not what you wanted to hear, but your ORM and your UI are clashing and that's the sort of thing that MVVM architecture (specifically separating the Model and ViewModel) are supposed to make better.

pete the pagan-gerbil
  • 3,136
  • 2
  • 28
  • 49
  • I think in the meantime I figured a way to refresh the list without doing the refreshing of lists at least through CollectionView.Refresh(). As for the rest, yes I kind of have realized my MVVM wasn't really completly true MVVM. I did it this way since I really didn't see why should I make basically copies of the model classes since they would expose the same properties. That would only needlessly complicate the codebase and make any changes to the DB a pain, I do acccept I might be commiting a terrible mistake, but so far it has worked wonderfully and by now the codebase isn't really small. – 4th Dimension Sep 11 '15 at 09:23
  • I do use View's fiew times where I actually need to do a transformation on the data before showing it, so there I do it properly. Load the data, generate the view send the view to the interface. – 4th Dimension Sep 11 '15 at 09:23