5

We currently have an application that allows users to create a Contract. A contract can have 1 or more Project. A project can have 0 or more sub-projects (which can have their own sub-projects, and so on) as well as 1 or more Line. Lines can have any number of sub-lines (which can have their own sub-lines, and so on).

Currently, our design contains circular references, and I'd like to get away from that. Currently, it looks a bit like this:

public class Contract
{
    public List<Project> Projects { get; set; }        
}

public class Project
{
    public Contract OwningContract { get; set; }
    public Project ParentProject { get; set; }
    public List<Project> SubProjects { get; set; }
    public List<Line> Lines { get; set; }
}

public class Line
{
    public Project OwningProject { get; set; }
    public List ParentLine { get; set; }
    public List<Line> SubLines { get; set; }
}

We're using the M-V-VM "pattern" and use these Models (and their associated view models) to populate a large "edit" screen where users can modify their contracts and the properties on all of the objects. Where things start to get confusing for me is when we add, for example, a Cost property to the Line. The issue is reflecting at the highest level (the contract) changes made to the lowest level.

Looking for some thoughts as to how to change this design to remove the circular references. One thought I had was that the contract would have a Dictionary<Guid, Project> which would contain ALL projects (regardless of their level in hierarchy). The Project would then have a Guid property called "Parent" which could be used to search the contract's dictionary for the parent object. THe same logic could be applied at the Line level.

Thanks! Any help is appreciated.

Thelonias
  • 153
  • 5

1 Answers1

2

Here are some thoughts:

  • What you've described is your Model, not your ViewModel. I've done MVVM on a tree structure before and it gets ugly fast. Your ViewModel needs to mimic this structure, but it should use ObservableCollection instead of List, and it can be more accepting of invalid data because the data's in transition while they're editing it.
  • Create code that will convert from your Model to your ViewModel when you load the screen. That allows you to implement a Cancel button that just does nothing.
  • I used events to propagate changes at lower levels in the tree up to higher nodes, and I would recommend not doing that. It would be better if the child node just called a method on the parent node, like PriceChanged(decimal oldPrice, decimal newPrice)
  • Consider all the actions that have to propagate: new children, re-ordering children, deleting children.
  • Decide up front if you want to implement Undo functionality because it's extremely hard to shoehorn it in afterwards. If you do want to have Undo, I suggest designing the tree such that every action has an easy and obvious reverse action, and then push that reverse action onto a Stack<Action>. You might want to pass a reference to that Stack to every node in the tree so you don't have to pass the undo actions up the tree for storage. Remember that in making every action have a reverse action, you have to think about PropertyChanged events too, so your View stays in sync.
  • You'll need code that validates the ViewModel before committing the changes to the Model, and you'll need code that commits the changes.
  • Think of the whole task as a data structure problem. Pre-conditions, actions, post-conditions. If you haven't used test driven development before, I suggest this would be an excellent place to start using it.
Scott Whitlock
  • 21,874
  • 5
  • 60
  • 88
  • Thanks for your response. I'll review your thoughts above. Do you have any recommendations for eliminating the circular references from the tree structure? That is, how can a child project know it's parent without directly having a reference to it's parent object? – Thelonias Oct 25 '12 at 19:29
  • 1
    I don't suggest eliminating them (it's a normal way to do a tree structure). However, the other way to do it is to store relationships as a separate set of entities. So a `Relationship` has a `Parent` property and a `Child` property. Then the nodes of the tree don't know about each other. I don't think that's a great idea though, because it's that much harder for nodes to notify each other when things change. – Scott Whitlock Oct 25 '12 at 19:35
  • If I could, I'd give an extra +1 for not worrying about the circular references. They're not related to the challenges the question is addressing. –  Oct 25 '12 at 21:05
  • @GlenH7 The circular references do cause me some pain though because we're using the DataContractSerializer (saving to XML as well as sending to a WCF service). I know we can use an attribute to mark an object as containing a reference, but this gets ugly quickly working up the object hierarchy. I was just curious if there was a cleaner way of doing things. I suppose I don't really need a "Parent" property on the Model...I can handle communication back to the parent via events. – Thelonias Oct 26 '12 at 14:57
  • @Ryan - please don't use events. You will eventually forget to unsubscribe one and then you just get weird stuff happening (consider the case when the user drags one node from one parent to another). If you don't want references, then use some kind of message bus (a common object that every node has a reference to where you can send a message like `PriceChanged` and all other nodes can use it as a trigger to recalculate prices, or do a top-down recalculation). – Scott Whitlock Oct 26 '12 at 16:38