具有聚合模型类的MVVM - 如何在ViewModels中包装?

时间:2010-08-31 18:26:52

标签: wpf mvvm

我目前正在尝试使用MVVM模式创建一个小应用程序。但是我真的不知道如何在我的ViewModel中正确包装聚合的Model类。从我对MVVM的了解不多,您不应该将ViewModel中的Models公开为属性,否则您可以直接从View中绑定到Model。所以我似乎必须将嵌套模型包装在另一个ViewModel中,但是这会在稍后同步Model和ViewModel时带来一些问题。

那么你如何有效地做到这一点?

我举一个简短的例子。假设我有以下模型类:

public class Bar
{
    public string Name { get; set; }
}

public class Foo
{
    public Bar NestedBar { get; set; }
}

现在我相应地创建了两个ViewModel类,包装了Models,但遇到了FooViewModel的问题:

public class BarViewModel
{
    private Bar _bar;
    public string Name 
    { 
        get { return _bar.Name; }
        set { _bar.Name = value; }
    }
}

public class FooViewModel
{
    private Foo _foo;
    public BarViewModel Bar
    {
        get { return ???; }
        set { ??? = value; }
    }
}

现在我该怎么处理FooViewModel的Bar属性?为了“get”工作,我需要返回一个BarViewModel实例。我是否在FooViewModel中创建了该类型的新字段,并将_foo.NestedBar对象包装在那里?对该字段属性的更改应向下传播到基础Bar实例,对吗?

如果我需要将另一个BarViewModel实例分配给该属性,如下所示:

foo.Bar = new BarViewModel();

现在它不会传播到模型,模型仍然保存类型为Bar的旧实例。我需要基于新的BarViewModel创建一个新的Bar对象,并将其分配给_foo,但是你如何优雅地做到这一点?在这个示例中,这是非常简单的,但是如果Bar有很多属性要复杂得多,那么打字会很多......更不用说它很容易出错,如果你忘了设置其中一个属性。

3 个答案:

答案 0 :(得分:7)

@Goblin

您的代码存在一些缺陷:例如:如果我从数据库中获取Foo对象的列表,并且我想将它们包装在ObservableCollection中,该怎么办?

那么你的FooViewModel的构造函数应该接受Foo模型作为参数而不是在构造函数中创建它!

通常你这样做是为了将模型包装到一个viewmodel中并将它同时放入一个可绑定的Collection中:

IEnumerable<Foo> foos = fooRepository.GetFoos();
foos.Select( m => viewmodelCollection.Add(new ViewModel(m,e.g.Service)));

模型属性不会复制到ViewModel地狱没有!!! ViewModel将其属性委托给模型属性,如:

public class FooViewModel
{
   private Foo _foo;

   public FooViewModel(Foo foo,IService service)
   {
      _foo = foo;

   }

   public string FoosName
   { 
      get{return _foo.Name };
      set
      {
         if(_foo.Name == value)
            return;

         _foo.Name = value;
         this.NotifyPropertyChanged("FoosName");
      }
   }

}

就像Goblin所说的所有UI特定界面一样:

IDataErrorInfo
INotifyPropertyChanged
IEditableObject

等...

仅由ViewModel实现。

答案 1 :(得分:3)

我的上述答案只有在你做DDD时才有意义 - 如果你不这样做 - 你可以解决这个问题 - 只需“压扁”模型:

public class FooViewModel
{
    private Foo _foo;
    public string Name
    {
        get { return _foo.Name; }
        set { _foo.Name = value; }
    }
    public string BarProperty
    {
        get { return _foo.Bar.Property; }
        set { _foo.Bar.Property = value; }
    }
}

或者你可以像我在前面的例子中看到的那样做 - 只是忽略聚合的一切......应该仍然有用。

答案 2 :(得分:1)

好的 - 首先要做的事情 - 使用“聚合”一词意味着你们正在坚持使用DDD?如果你是 - 你正在做一个封装禁止: - )。绝不允许一个Aggregate编辑另一个Aggregate。如果你拥有的是两者都是聚合的,那么它们就会变得联系起来(这在DDD意义上是完全“合法的” - 但是你在FooViewModel上的问题不会是BarViewModel类型,而是类型Bar。那样吧将(因为它应该)负责更新自己 - 我们只维护FooViewModel中的链接。

但是,如果你正在做的是带有ValueType子级的AggregateRoot - 那么在给出略微修改的域模型的情况下,你可以做到这一点:

public class Foo
{
    public string SomeProperty { get; set; }
    public Bar Bar { get; set; }
    public void Save()
    {
        //Magically saves to persistent storage...
    }
}
public class Bar
{
    public Bar(string someOtherProperty)
    {
        SomeOtherProperty = someOtherProperty;
    }
    public string SomeOtherProperty { get; private set; }
}

然后是ViewModels:

public class FooViewModel
{
    private Foo _foo;
    public FooViewModel()
    {
        Bar = new BarViewModel();
    }
    public BarViewModel Bar { get; private set; }
    public void SetFoo(Foo foo)
    {
        _foo = foo;
        SomeProperty = foo.SomeProperty;
        Bar.SetBar(foo.Bar);
    }
    public string SomeProperty { get; set; }
    public void SaveChanges()
    {
        _foo.SomeProperty = SomeProperty;
        _foo.Bar = Bar.CreateUpdatedBar();
        _foo.Save();
    }
}
public class BarViewModel
{
    public string SomeOtherProperty { get; set; }
    public void SetBar(Bar bar)
    {
        SomeOtherProperty = bar.SomeOtherProperty;
    }
    public Bar CreateUpdatedBar()
    {
        return new Bar(SomeOtherProperty);
    }
}

这样--FooViewModel现在能够控制BarViewModel(它只接受一个值类型 - 并在被问到时创建一个新的类型)。这也解决了一个常见的UI问题('我们如何编辑没有setter的对象?' - 回答:'我们没有 - 我们创建一个新的'')。缺少很多充实的东西(INotifyPropertyChanged,脏跟踪等等,但是如果你经历了这种思维的飞跃,这些很容易: - )。

我希望这有点意义:-)否则,我会很乐意详细说明。