MVVM: привязка к модели при сохранении модели в синхронизации с версией сервера

Я потратил довольно много времени, чтобы попытаться найти элегантное решение для следующей задачи. Я не смог найти решение, которое больше, чем взломать проблему.

У меня простая настройка View, ViewModel и модели. Я сделаю это очень просто для объяснения.

  • Model имеет одно свойство Title типа String.
  • Model - это DataContext для View.
  • View имеет TextBlock, который привязывается к Title на модели.
  • ViewModel имеет метод под названием Save(), который сохранит Model до Server
  • Server может вносить изменения, сделанные в Model

Пока все хорошо. Теперь мне нужно сделать две настройки, чтобы синхронизировать модель с Server. Тип сервера не важен. Просто знайте, что мне нужно вызвать Save(), чтобы подтолкнуть модель к Server.

Настройка 1:

  • Свойству Model.Title необходимо вызвать RaisePropertyChanged(), чтобы перевести изменения, сделанные в Model на Server, на View. Это хорошо работает, поскольку Model является DataContext для View

Неплохо.

Настройка 2:

  • Следующий шаг - вызвать Save(), чтобы сохранить изменения, сделанные с View, на Model на Server. Здесь я застреваю. Я могу обработать событие Model.PropertyChanged на ViewModel, которое вызывает Save(), когда модель изменена, но это делает ее эхо-изменения, сделанные сервером.

Я ищу элегантное и логичное решение и желаю изменить свою архитектуру, если это имеет смысл.

Ответ 1

В прошлом я написал приложение, которое поддерживает "живое" редактирование объектов данных из нескольких мест: многие экземпляры приложения могут редактировать один и тот же объект одновременно, а когда кто-то нажимает на сервер изменения всех остальных получает уведомление и (в простейшем случае) немедленно видит эти изменения. Вот краткое описание того, как оно было разработано.

Настройка

  • Представления всегда привязываются к ViewModels. Я знаю, что это много шаблонов, но привязка непосредственно к Модели неприемлема ни в одном, ни в простейшем сценарии; это также не в духе MVVM.

  • ViewModels несут исключительную ответственность за нажатие изменений. Это, очевидно, включает в себя нажатие изменений на сервере, но оно также может включать в себя нажатие изменений на другие компоненты приложения.

    Для этого ViewModels могут захотеть клонировать модели, которые они переносят, чтобы они могли предоставлять семантику транзакции остальной части приложения, поскольку они предоставляют серверу (т.е. вы можете выбрать, когда нужно вносить изменения в остальную часть приложения, которую вы не можете сделать, если все напрямую привязаны к одному экземпляру модели). Выделение таких изменений требует еще большей работы, но также открывает мощные возможности (например, отменять изменения тривиально: просто не нажимайте их).

  • ViewModels зависят от какой-либо службы данных. Служба данных - это компонент приложения, который находится между хранилищем данных и потребителями и обрабатывает всю связь между ними. Всякий раз, когда ViewModel клонирует свою Модель, он также подписывается на соответствующие события "хранилище данных", которые предоставляет Служба Данных.

    Это позволяет ViewModels получать уведомления об изменениях в "своей" модели, которые другие ViewModels помещаются в хранилище данных и реагируют соответствующим образом. При правильной абстракции хранилище данных может также быть вообще чем угодно (например, служба WCF в этом конкретном приложении).

Workflow

  • Создана ViewModel и назначена принадлежность модели. Он немедленно клонирует модель и предоставляет этот клон представлению. Имея зависимость от службы данных, он сообщает DS, что он хочет подписаться на уведомления для обновлений этой конкретной модели. ViewModel не знает, что именно идентифицирует свою модель ( "первичный ключ" ), но это не нужно, потому что это ответственность DS.

  • Когда пользователь заканчивает редактирование, они взаимодействуют с представлением, которое вызывает команду на виртуальной машине. Затем VM вызывает в DS, нажав изменения, внесенные в свою клонированную модель.

  • DS сохраняет изменения и дополнительно вызывает событие, которое уведомляет все другие заинтересованные виртуальные машины, которые были изменены в Модели X; новая версия модели предоставляется как часть аргументов события.

  • Другие виртуальные машины, которым было присвоено право собственности на одну и ту же модель, теперь знают, что внешние изменения прибыли. Теперь они могут решить, как обновить представление, имея все части головоломки под рукой ( "предыдущая" версия модели, которая была клонирована, "грязная" версия, которая является клоном, и "текущая" версия, которая был нажат как часть аргументов события).

Примечания

  • Модель INotifyPropertyChanged используется только View; если ViewModel хочет знать, является ли модель "грязной", она всегда может сравнивать клон с исходной версией (если она была сохранена, что я рекомендую, если это возможно).
  • ViewModel автоматически перенаправляет изменения на Сервер, что хорошо, потому что это гарантирует, что хранилище данных всегда находится в согласованном состоянии. Это выбор дизайна, и если вы хотите сделать что-то по-другому, более подходящим будет другой дизайн.
  • Сервер может отказаться не поднимать событие "Измененная модель" для ViewModel, которое отвечало за это изменение, если ViewModel передает this в качестве параметра для вызова "push changes". Даже если это не так, ViewModel может ничего не делать, если видит, что "текущая" версия модели идентична его собственному клону.
  • При достаточной абстракции изменения могут быть перенесены на другие процессы, запущенные на других машинах, так же легко, как их можно переместить в другие виды в вашей оболочке.

Надеюсь, что это поможет; При необходимости я могу предложить дополнительные разъяснения.

Ответ 2

Я бы предложил добавить контроллеры в MVVM mix (MVCVM?), чтобы упростить шаблон обновления.

Контроллер прослушивает изменения на более высоком уровне и распространяет изменения между Model и ViewModel.

Основные правила, чтобы сохранить чистоту:

  • ViewModels - это просто тупые контейнеры, которые содержат определенную форму данных. Они не знают, откуда поступают данные или где они отображаются.
  • В представлениях отображается определенная форма данных (через привязки к модели представления). Они не знают, откуда берутся данные, только как отображать их.
  • Модели поставляют реальные данные. Они не знают, где они потребляются.
  • Контроллеры реализуют логику. Такие вещи, как предоставление кода для ICommands в виртуальных машинах, прослушивание изменений данных и т.д. Они заполняют виртуальные машины от моделей. Имеет смысл заставить их слушать изменения VM и обновлять модель.

Как уже упоминалось в другом ответе, ваш DataContext должен быть VM (или его собственностью), а не моделью. Указание на DataModel затрудняет разделение проблем (например, для Test Driven Development).

Большинство других решений ставят логику в ViewModels, которая "не права", но я вижу, что преимущества контроллеров все время игнорируются. Притворись, что акроним MVVM!:)

Ответ 3

привязка модели для прямого просмотра работает только в том случае, если модель реализует интерфейс INotifyPropertyChanged. (например, ваша модель, сгенерированная платформой Entity Framework)

Реализация модели INotifyPropertyChanged

вы можете это сделать.

public interface IModel : INotifyPropertyChanged //just sample model
{
    public string Title { get; set; }
}

public class ViewModel : NotificationObject //prism ViewModel
{
    private IModel model;

    //construct
    public ViewModel(IModel model)
    {
        this.model = model;
        this.model.PropertyChanged += new PropertyChangedEventHandler(model_PropertyChanged);
    }

    private void model_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "Title")
        {
            //Do something if model has changed by external service.
            RaisePropertyChanged(e.PropertyName);
        }
    }
    //....more properties
}

ViewModel как DTO

Если Model реализует INotifyPropertyChanged (это зависит), вы можете использовать его как DataContext в большинстве случаев. но в DDD большинство моделей MVVM будут рассматриваться как EntityObject, а не настоящая модель домена.

более эффективным способом является использование ViewModel как DTO

//Option 1.ViewModel act as DTO / expose some Model property and responsible for UI logic.
public string Title
{
    get 
    {
        // some getter logic
        return string.Format("{0}", this.model.Title); 
    }
    set
    {
        // if(Validate(value)) add some setter logic
        this.model.Title = value;
        RaisePropertyChanged(() => Title);
    }
}

//Option 2.expose the Model (have self validation and implement INotifyPropertyChanged).
public IModel Model
{
    get { return this.model; }
    set
    {
        this.model = value;
        RaisePropertyChanged(() => Model);
    }
}

оба свойства ViewModel выше могут использоваться для привязки, не нарушая шаблон MVVM (pattern!= rule), это действительно зависит.

Еще одна вещь..  ViewModel имеет зависимость от модели. если модель может быть изменена внешним сервисом/средой. это "глобальное состояние", которое усложняет ситуацию.

Ответ 4

Если ваша единственная проблема заключается в том, что изменения с сервера немедленно восстанавливаются, почему бы не сделать что-то вроде следующего:

//WARNING: typed in SO window
public class ViewModel
{
    private string _title;
    public string Title
    {
        get { return _title; }
        set
        {
            if (value != _title) 
            {
                _title = value;
                this.OnPropertyChanged("Title");
                this.BeginSaveToServer();
            }
        }
    }

    public void UpdateTitleFromServer(string newTitle)
    {
        _title = newTitle;
        this.OnPropertyChanged("Title"); //alert the view of the change
    }
}

Этот код вручную предупреждает представление об изменении свойства с сервера, не проходя через средство настройки свойств и, следовательно, не вызывая код "сохранить на сервер".

Ответ 5

Причина, по которой у вас возникла эта проблема, заключается в том, что ваша модель не знает, загрязнен ли она или нет.

string Title {
  set {
    this._title = value;
    this._isDirty = true; // ??!!
  }
}}

Решение состоит в том, чтобы скопировать изменения сервера с помощью отдельного метода:

public void CopyFromServer(Model serverCopy)
{
  this._title = serverCopy.Title;
}