In this post I will write about some problems I faced with INotifyPropertyChanged while using MVVM and some solutions available.
Let’s first examine the problem.
Is INotifyPropertyChanged an anti-pattern or not?
The short answer is yes? But it really depends on how big is the project. It is very common to use PropertyChanged events inside ViewModels of an MVVM application to propagate changes occurring in your classes back to presentation layer via WPF data binding mechanism.
So why the size of the project matters. Well, it all comes down to maintainability of the code base. PropertyChange events are usually raised by passing property names as string constants (that’s a bummer). If the number of view models and their properties is large, the project will have too many hard coded strings. Yes, it is possible to move these strings at the beginning of the class file, and there are very nice tools which can do that for you, such as Refactor! from DevExpress or even Visual Studio itself. But still if you are changing schema and your data model is regenerated then most likely corresponding view models will require changes as well and that is where maintenance becomes a nightmare on projects with more than 10 view models. Add to it all the business complexities and tracking control flow will be almost impossible. So the question basically is do you want to pollute your code with hardcoded string? If code is large – then probably NO, if code is small then I guess it’s OK.
Now let’s see what are the options (this list is not extensive).
1. Why not use reflection to get property names, when property names change we can simply recompile? So such solution was proposed by Karl Shifflett – a Program Manager on Patterns and Practices Prism Team, Microsoft Corporation. But later Sacha Barber found a flaw in such approach with Stack Frames. Karl since updated his post and commented on this problem. Basically if you compile your code for release and remove pdb (debug database with initial method and property names) files, due to compiler optimization the property names will not be properly resolved and some calls will end up in the wrong place inside the stack :(. Sacha since proposed his own framework for MVVM called Cinch. While it sounds like a solid piece of work it doesn’t really address PropertyChanged problem.
2. Josh Smith blogged about this problem as well and proposed using lambda expressions to validate property names at compile time, thus avoiding StackFrame problem. He used a property observer pattern and created a generic class (PropertyObserver<TPropertySource>) to handle it along with weak event listeners. Still this approach requires changes to be propagates when properties change. While again there are tools which can do this in semi-automated fashion it could be error prone when there are too many dependencies between properties within and outside of the class.
3. Another approach is to use weak event referencing for all independent properties. Such solution called UpdateControls was proposed by Michael Perry. He has a great set of videos on his web site explaining this approach. And this is wonderful, but it because an overkill when working with large collections. It takes up too much memory to register for every single independent property in let’s say a list of 100 000 records.
4. My approach. I decided to use a mix of things from the above.
- All my Data Model classes are generated and use Property Changed events. Since there is no business logic which resides in these classes there is no complexity associated in maintaining custom event handlers or raising of events. Everything is generated and regenerated automatically when database schema changes.
- For my view models I used a similar approach. But use UpdateControl for the properties. Partial view model classes are generated based off of a database schema (dbml XML model file) using slightly modified T4 template provided by Damien. Here is my template below.
These templates are very similar to ASP, but reading such templates is very hard. So to read them easily there is a special and FREE markup tool developed by Tangible Engineering and a nice intro article written by Oleg Sych.
- For collections I use ObservableCollections thus disabling UpdateControls for them and preventing creation of millions of event handlers and events for large collections.
- For every object which gets into the view or somehow selected by a user a view model is generated. This dramatically improves performance of UpdateControls while still gives me a no worry approach for my PropertyChange events. Each view model may have custom validation logic or anything else since all classes are generated as partial.
- Having business logic in model views also helps working around some limitations of LINQ to SQL 3.5. Such as inability to detach entities, undo operations or working in concurrent scenarios with multiple threads.
Having being able to find solution to all these problems I realized that such approach brings many additional benefits to the table. In simple words in my application:
- all lists are ObservableCollections of data models. Since data models are also partial classes, they can be easily extended with additional functionality.
- users make changes only to view models. Thus having the ability to roll back changes if database errors occur or having the ability to change multiple objects at the same time. Both of these things are impossible with current LINQ to SQL, since all the changes are made directly to the model and there is no way to detach any objects. The whole model needs to be disposed. While disposing the whole model means all change tracking that is built into LINQ to SQL classes is useless. Now in my model I get to enjoy the benefits of change tracking, and the ability to roll back changes. When objects need to be saved (persisted) to a data store, it is done in a separate instance of database DataContext. If errors occur I throw away such temporary data context, since I can’t detach entity from the model. And notify users about the error. If everything is fine, the new entity gets attached to the current model, or changed entity is refreshed from the data store.
- another benefit is data mining. Since I am using only one data context internally I am able to do joins between multiple lists and run queries of different complexities. Current LINQ to SQL doesn’t allow queries to overlap collections from different instances of the same data context.
So this was another not so easy, but still ISolvable<TProblem>.
Happy Coding and have a wonderful New Year!