Best practice for keeping Models and ViewModels in sync
I'm working on a large Silverlight application that uses duplex Net.TCP to talk to a WCF back-end. I'm in the process of moving this app from an MVC approach to MVVM. However, I'm struggling with the right way to implement my ViewModels. We're using the WCF-generated proxies for our model, which is quite complex, involving dozens of classes, lots of collections, and a variety of many-to-many relationships. For instance, a User can belong to many Rooms, a Room can have many Users, a User can have many SharedFiles, and each SharedFile can be shared with any Room that the User is currently a part of. That sort of thing.
On top of that, because we're using WCF in a duplex mode, changes to the model can be triggered either by the end-user, or by the WCF service on the back-end. In other words, the model we're using is several orders of magnitude more complicated than the typical "Model" that you see represented in any of the various MVVM books/articles/blog posts. And that's where the problem comes in, because keeping our ViewModel layer in sync with the underlying Model layer is becoming a bit of a hassle.
Here's a typical problem. A new "User" joins a "Room", so the WCF services fires off a "SessionAdded" notification to all the other users in the room. The SessionAdded notification carries along a Session object which has a linked Room and a linked User object. This Room object that gets deserialized from the WCF service is basically the same as the Room object on the local client, and probably has most of the same data, but it certainly doesn't have all the same data, and at least some of the data (like its null Whiteboards collection) is certainly wrong. So we need to somehow take this incoming data and merge it into our existing model. And then we need to create ViewModels on top of each of the new objects, and/or update existing ViewModels with the new objects and/or their data.
Right now we're handling this by having the various ViewModels respond to the relevant WCF notification events, and do their best to fix up their underlying models and related view models. We've figured out a few tricks, like a SynchronizedObservableCollection (vaguely like the one here) which watches (say) the Room.Sessions ObservableCollection and automatically creates corresponding SessionViewModels and places them in the RoomViewModel.SessionViewModels collection. We're also using a ViewModelFactory, which caches the view models and ensures that, say, the SessionViewModel which wraps a given Session stays the same, even if the underlying Session object gets changed out. (If it matters, we're using a viewmodel-first approach, sinc开发者_JAVA技巧e a big chunk of what we need is for new UI elements to be created in response to changes in the ViewModel triggered by our WCF notifications.)
And all of this works. Basically. Most of the time. You know. But it's a lot of code to maintain, and it's easy to get wrong. Unit tests are handy so long as you can remember what's supposed to happen, but by the time you get done processing your 20th cascading CollectionChanged event, it's hard to keep track of how all this fits together and what you were testing for in the first place. In other words, it's all pretty damned brittle.
It seems to me that this is the sort of scenario that lots of people must have run into, and I'm curious how other folks have faced it down. I can think of a couple approaches to perhaps make it better:
(1) Treat the client-side Model as a sort of database which needs to be kept completely coherent, and implement a client-side data access layer whose job it is to keep the model consistent. All updates to the model, whether from the user or the server, would need to go through this layer. It would be a little like the Entity Framework, in that myRoom.Users.Add(myUser)
would automatically set myUser.Room = myRoom
, and vice versa, and so forth. (This especially is the part that it seems like someone somewhere should have already developed, though I haven't found it yet.)
(2) Lean on something like Truss or Obtics, to keep all the pieces in sync. Not quite sure how that would work yet, but it sounds in theory like it should be possible.
And... what else? I'm curious as to the patterns or frameworks that have been used to solve this problem.
I understand your pain - I am currently developing a complex data visualization application using the MVVM pattern. One really important question to ask yourself is, "Does the View Model add value everywhere that you ar eusing it?", in ther words, are there places where it is simply forwarding properties on your model layer to your view?
I often find that there areas of code, typically at the detail level (e.g. the properties of a Person object, Age, Name, Forename), where the view model is not actually adding any value at all, whilst at the more course level, it adds value by structuring views / windows etc...
I tend to take an adaptive approach to MVVM, at the top level (Windows, panes, forms) I always have a view model, but if parts of the view model are so simple that a view model adds no value, I expose them to the view directly. Also, in some cases you need to expose the model directly to the view for better performance.
Finally, if you find that you need to re-introduce a view model to solve a tricky binding issue, I wrote about a simple pattern, mini-MVVM, which applies a local view model:
http://www.scottlogic.co.uk/blog/colin/2009/08/the-mini-viewmodel-pattern/
Hope that helps.
精彩评论