4 Stimmen

MVVM mit aggregierten Modellklassen - wie in ViewModels einpacken?

Ich versuche gerade, eine kleine Anwendung nach dem MVVM-Muster zu erstellen. Allerdings weiß ich nicht wirklich, wie man richtig verpacken aggregierte Modellklassen in meinem ViewModel. Nach dem, was ich über MVVM weiß, sollen Sie keine Modelle in Ihrem ViewModel als Eigenschaften anzeigen, da Sie sonst direkt von Ihrer Ansicht aus an das Modell binden könnten. Es scheint also, dass ich das verschachtelte Model in ein anderes ViewModel verpacken muss, aber das bringt einige Probleme mit sich, wenn ich Model und ViewModel später synchronisiere.

Wie kann man das also effizient machen?

Ich werde ein kurzes Beispiel geben. Nehmen wir an, ich habe die folgenden Modellklassen:

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

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

Nun erstelle ich zwei entsprechende ViewModel-Klassen, die die Models umhüllen, stoße aber auf Probleme mit dem 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; }
    }
}

Was mache ich nun mit der Bar-Eigenschaft von FooViewModel? Damit "get" funktioniert, muss ich eine BarViewModel-Instanz zurückgeben. Erstelle ich ein neues Feld dieses Typs in FooViewModel und verpacke das _foo.NestedBar-Objekt einfach darin? Änderungen an den Eigenschaften dieses Feldes sollten sich auf die zugrunde liegende Bar-Instanz übertragen, richtig?

Was ist, wenn ich dieser Eigenschaft eine andere BarViewModel-Instanz zuweisen muss, etwa so:

foo.Bar = new BarViewModel();

Jetzt wird das nicht auf das Modell übertragen, das immer noch die alte Instanz des Typs Bar enthält. Ich müsste ein neues Bar-Objekt erstellen, das auf dem neuen BarViewModel basiert und es _foo zuordnen, aber wie kann man das elegant machen? In diesem Beispiel ist es ziemlich trivial, aber wenn Bar viel komplexer ist und viele Eigenschaften hat, wäre das eine Menge Tipparbeit... ganz zu schweigen davon, dass es sehr fehleranfällig wäre, wenn man vergisst, eine der Eigenschaften zu setzen.

7voto

Elisabeth Punkte 19382

@Kobold

Es gibt einige Fehler in Ihrem Code: z.B. was, wenn ich eine Liste von Foo-Objekten aus der Datenbank erhalte und jedes von ihnen in eine ObservableCollection einpacken möchte?

dann sollte Ihr Konstruktor von FooViewModel das Foo-Modell als Parameter akzeptieren und es nicht innerhalb des Konstruktors erstellen!

Normalerweise macht man das, um ein Modell in ein Viewmodel zu verpacken und es gleichzeitig in eine bindbare Collection zu packen:

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

Die Eigenschaften des Modells werden nicht in das ViewModel kopiert!!! Das ViewModel delegiert seine Eigenschaften an die Modelleigenschaften wie:

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");
      }
   }

}

Und wie Goblin sagte, alle UI-spezifischen Schnittstellen wie:

IDataErrorInfo
INotifyPropertyChanged
IEditableObject

usw...

werden NUR durch das ViewModel implementiert.

3voto

Goblin Punkte 7780

Meine obige Antwort macht nur Sinn, wenn Sie DDD tun - wenn Sie nicht - Sie können Ihr Problem so lösen - einfach "Abflachung" das Modell:

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; }
    }
}

Oder Sie könnten tun, wie ich im vorherigen Beispiel gezeigt - ignorieren Sie einfach alles über Aggregate... sollte immer noch funktionieren.

1voto

Goblin Punkte 7780

Okay - das Wichtigste zuerst - die Verwendung des Begriffs Aggregate impliziert, dass Sie sich an DDD halten? Wenn ja, dann tun Sie ein Kapselungsverbot :-). Ein Aggregat sollte niemals ein anderes Aggregat bearbeiten dürfen. Wenn beide Aggregate wirklich Aggregate sind, würden sie assoziiert werden (was im DDD-Sinne völlig 'legal' ist - aber dann wäre deine Eigenschaft auf dem FooViewModel nicht vom Typ BarViewModel, sondern vom Typ Bar. Auf diese Weise würde Bar (wie es sollte) für die Aktualisierung selbst verantwortlich sein - und wir pflegen nur den Link in FooViewModel.

Allerdings, wenn das, was Sie tun, ist AggregateRoot mit einem ValueType Kind - dann ist hier, was Sie angesichts einer leicht modifizierten Domänenmodell tun könnte:

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; }
}

Und dann für die 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);
    }
}

Auf diese Weise ist das FooViewModel nun in der Lage, das BarViewModel zu steuern (das nichts anderes tut, als einen Wertetyp zu akzeptieren - und bei Bedarf einen neuen zu erstellen). Dies löst auch ein häufiges UI-Problem ('Wie bearbeiten wir ein Objekt, das keine Setter hat?' - Antwort: 'Tun wir nicht - wir erstellen ein neues'). Es fehlt noch eine Menge an Details (INotifyPropertyChanged, Dirty-Tracking etc.), aber die sind einfach, wenn man diesen Gedankensprung überstanden hat :-).

Ich hoffe, das macht ein bisschen Sinn :-) Andernfalls werde ich das gerne näher erläutern.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X