WPF/MVVM: Delegating a domain Model collection to

2020-03-30 15:48发布

问题:

A domain model collection (normally a List or IEnumerable) is delegated to a ViewModel.

Thats means my CustomerViewModel has a order collection of type List or IEnumerable.

No change in the list is recognized by the bound control. But with ObservableCollection it is.

This is a problem in the MVVM design pattern.

How do you cope with it?

UPDATE: Sample of how I do it:

 public class SchoolclassViewModel : ViewModelBase
{
    private Schoolclass _schoolclass;
    private ObservableCollection<PupilViewModel> _pupils = new ObservableCollection<PupilViewModel>();        

    public SchoolclassViewModel(Schoolclass schoolclass)
    {
        _schoolclass = schoolclass;
        _schoolclass.Pupils = new List<Pupil>();

        foreach (var p in schoolclass.Pupils)           
            Pupils.Add(new PupilViewModel(p));            
    }

    public Schoolclass GetSchoolclass 
    {
        get { return _schoolclass; } 
    }

    public int ID { get; set; }       

    public string SchoolclassName
    {
        get { return _schoolclass.SchoolclassName;}
        set
        { 
            if(_schoolclass.SchoolclassName != value)
            {                    
                _schoolclass.SchoolclassName = value;
                this.RaisePropertyChanged("SchoolclassName");
            }

        }
    }   

    public ObservableCollection<PupilViewModel> Pupils
    {
        get{ return _pupils;}
        set
        {
            _pupils = value;
            this.RaisePropertyChanged("Pupils");
        } 
    }
}

回答1:

Ok, I'll go ahead and add my thoughts as an answer instead of in the comments. :)

I think the bottom line is that this is just the reality of the way WPF and databinding work. In order for two-way databinding to operate, collections need a means of notifying controls that are bound to them, and the standard lists and collections used in most domain objects don't/won't/shouldn't support this. As I mentioned in a comment, being required to implement INotifyPropertyChanged for non-collection properties is another requirement that may not be met by a standard domain object.

Domain objects are not intended to to be viewmodels, and for this reason you may find that you need to map back and forth between the two types of objects. This is not dissimilar to having to map back and forth between domain objects and data access objects. Each type of object has a different function in the system, and each should be specifically designed to support their own role in the system.

All that said, Agies's idea of using AOP to automatically generate proxy classes is very interesting, and something I intend to look into.



回答2:

I deal with this by not doing it the way you describe.

If I need to present a Foo object and its related Bar objects in the view, the FooViewModel will generally implement a Bars property of type ObservableCollection<BarViewModel>.

Note that this is irrespective of whether or not the underlying Foo class has a Bars property of type IEnumerable<Bar>. The Foo class might not. The application might not even need to be able to iterate over all of the Bar objects for a Foo, except in the UI.

Edit

When my view is a simple representation of the application's object model, I pretty much do things as you do in your sample. The code in my constructor is generally a bit more compact:

_Bars = new ObservableCollection<BarViewModel>(
   _Foo.Bars.Select(x => new BarViewModel(x)));

but it's essentially the same thing.

But this assumes that Foo actually exposes a Bars property. It might not. Or maybe only some Bar objects should appear in the view. Or maybe they should appear intermingled with other objects, and the FooViewModel should expose a CompositeCollection of some kind.

The point I'm making is that the view model is a model of the view. This doesn't necessarily have a direct correspondence to the underlying object model.

To pick a simple example: My program may give the user a way of putting items into five different categories by dragging and dropping them into five different ListBox controls. Ultimately, doing this sets a Category property on the Item object. My view model is going to have a collection of CategoryViewModel objects, each with a property of type ObservableCollection<ItemViewModel>, so that dragging items back and forth between collections is simple to implement.

The thing is, there may not even be a Category class in the application's object model, let alone a collection of Category objects. Item.Category might just be a property of type string. The CategoryViewModel isn't mirroring the application's object model. It only exists to support the view in the UI.



回答3:

What I do is instead of using ObservableCollection in my domain model is use my own collection type that implements the INotifyCollectionChanged interface amongst other useful standard and custom interfaces. My way of thinking is that much like Rockford Lhotka suggests in his book that change notification is useful in to more than just a presentation layer since other business objects within the domain layer often need some sort of notification when state changes in another object.

With this methodology you could create your own collection type that still has the benefits of change notification and as well as what ever custom behaviors you need. The base class for your collection could be implemented as purely infrastructure code and then a subclass could be created that could contain business logic using the subtype layering techinque used in this book. So in the end you could have a collection that can wrap types of IEnumerable<> and provide the change notification stuff your looking for as well for both your domain model and presentation code.