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.
Here are some thoughts:
- What you’ve described is your
Model
, not yourViewModel
. I’ve done MVVM on a tree structure before and it gets ugly fast. YourViewModel
needs to mimic this structure, but it should useObservableCollection
instead ofList
, 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 yourViewModel
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 thatStack
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 aboutPropertyChanged
events too, so yourView
stays in sync. - You’ll need code that validates the
ViewModel
before committing the changes to theModel
, 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.
5