Уведомить ViewModel об асинхронных изменениях в модели

Я относительно новичок в MVVM, и мне интересно, как лучше всего структурировать мое приложение. Вот образец моих моделей:

  public class ModelSource : ModelBase
  {
    #region Fields

    private int _isLoading;

    private BackgroundWorker worker = new BackgroundWorker();

    private ObservableCollection<PCDatabase> _databases;

    #endregion //Fields

    #region Properties

    public ObservableCollection<PCDatabase>Databases
    {
        get
        {
            if (_databases == null)
            {

                _databases = new ObservableCollection<PCDatabase>();
            }
            return _databases;
        }
        set
        {
            _databases = value;
            this.OnPropertyChanged("Databases");
        }
    }

    public int IsLoading
    {
        get
        {
            return _isLoading;
        }

        set
        {
            _isLoading = value;
            OnPropertyChanged("IsLoading");
        }
    }


    #endregion

    #region Methods

    /// <summary>
    /// Gets all Databases from the Server
    /// </summary>
    public void getDatabasesAsync(ConfigDatabaseConnection _currentConfig)
    {
      //execute SQL Query...
    }

(ModelBase реализует INotifyPropertyChanged).

Вот моя соответствующая ViewModel:

namespace DbRestore.ViewModel
{
public class ViewModelSource : ViewModelBase
{
    private ObservableCollection<PCDatabase> _databases;

    private ModelSource _modelSource;

    private ICommand _populateDatabaseCommand;

    public ConfigDatabaseConnection _currentConfig;

    public ViewModelSource()
    {
        this.ModelSource = new ModelSource();
    }

    #region Commands

    /// <summary>
    /// Command that opens a Database Connection Dialog 
    /// </summary>
    public ICommand OpenDataBaseConnectionCommand
    {
        get
        {
            if (_populateDatabaseCommand == null)
            {
                _populateDatabaseCommand = new RelayCommand(
                    param => this.PopulateDatabases()
                    );
            }
            return _populateDatabaseCommand;
        }
    }

    public ObservableCollection<PCDatabase> Databases
    {
        get
        {
            return _databases;
        }

        set
        {
            _databases = value;
            OnPropertyChanged("Databases");
        }
    }

    #endregion //Commands

    public void PopulateDatabases()
    {
        ModelSource.getDatabasesAsync(_currentConfig);
    } 

Вызов ModelSource.getDatabasesAsync (_currentConfig) получает мои данные SQL в моей модели. Из-за того, что некоторые из моих SQL-запросов довольно сложны, я реализовал Background Worker, который выполняет эти запросы асинхронно.

Как мне получить данные в моей ViewModel, которая привязана к моему View? Или мой подход к дизайну в целом ошибочен?

Вещи, которые я рассмотрел и попробовал:

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

  • Перенос SQL-запросов в ViewModel: тоже работает, но тогда мой класс Model кажется избыточным - это был бы не что иное, как настраиваемый тип данных.

  • Выполняйте запросы синхронно и напрямую назначайте Observable Collection в моей модели наблюдаемой коллекции в моей ViewModel. Также работает, но тогда у меня возникают проблемы с моим BackgroundWorker, потому что ViewModel не узнает, когда запрос действительно завершен.


person Fang    schedule 10.02.2016    source источник
comment
Подпишитесь на Модель PropertyChanged, проверьте, была ли она nameof(ModelSource.Databases), и если она стала false, данные загружаются (?), Обновите свойства, используемые в привязках.   -  person Sinatr    schedule 10.02.2016
comment
О вещах, которые вы учли: 1) нет, представление и модель не должны иметь никаких отношений 2) Нет, запросы sql должны быть на уровне службы / DAO 3) Нет, модель не должна знать ViewModel (потому что несколько моделей ViewModel могут использовать модель ). Правильный способ - вызвать событие, перехватываемое объектом ViewModel.   -  person nkoniishvt    schedule 10.02.2016
comment
@Nkoniishvt: Спасибо. Информация, которую вы предоставили, коррелирует с моими выводами. Моя проблема в том, что я действительно зеленый, когда дело доходит до WPF И С #, приложение, которое мне нужно создать, довольно сложное, и есть много информации о WPF / MVVM, некоторые из них довольно устаревшие. Не могли бы вы указать мне на хороший учебник, который включает в себя уровень службы / DAO?   -  person Fang    schedule 10.02.2016


Ответы (2)


  1. Переместите всю логику вашей базы данных в класс службы (он же репозиторий).
  2. Можно выполнять прямую привязку к свойствам модели вместо создания дюжины прокси-классов ViewModel для каждой модели, если вам не нужна какая-либо специальная логика, связанная с представлением, для конкретной модели. Так что выставлять коллекцию PCDatabase - нормально.

Поскольку вы используете BackgroundWorker, я предполагаю, что вы используете .NET Framework 3.5 и не имеете TPL.

public interface IPCDatabaseRepository
{
    void GetPCDatabasesAsync(Action<IList<PCDatabase>> resultHandler);
}

public class PCDatabaseRepository : IPCDatabaseRepository
{
    public void GetPCDatabasesAsync(Action<IList<PCDatabase>> resultHandler)
    {
        var worker = new BackgroundWorker();

        worker.DoWork += (sender, args) =>
        {
            args.Result = // Execute SQL query...
        };  

        worker.RunWorkerCompleted += (sender, args) =>
        {
            resultHandler(args.Result as IList<PCDatabase>);
            worker.Dispose();
        };

        worker.RunWorkerAsync();
    }
}

public class ViewModelSource : ViewModelBase
{
    private readonly IPCDatabaseRepository _databaseRepository;
    private ObservableCollection<PCDatabase> _databases;
    private bool _isBusy;

    public ViewModelSource(IPCDatabaseRepository databaseRepository /*Dependency injection goes here*/)
    {
        _databaseRepository = databaseRepository;
        LoadDatabasesCommand = new RelayCommand(LoadDatabases, () => !IsBusy);
    }

    public ICommand LoadDatabasesCommand { get; private set; }

    public ObservableCollection<PCDatabase> Databases
    {
        get { return _databases; }
        set { _databases = value; OnPropertyChanged("Databases"); }
    }

    public bool IsBusy 
    { 
        get { return _isBusy; }
        set { _isBusy = value; OnPropertyChanged("IsBusy"); CommandManager.InvalidateRequerySuggested(); }
    }

    public void LoadDatabases()
    {
        IsBusy = true;

        _databaseRepository.GetPCDatabasesAsync(results =>
        {
            Databases = new ObservableCollection(results);
            IsBusy = false;
        });
    }
person Yarik    schedule 10.02.2016

Вы видели эти статьи?

Асинхронное программирование: шаблоны для асинхронных приложений MVVM: привязка данных https://msdn.microsoft.com/en-us/magazine/dn605875.aspx

Асинхронное программирование: шаблоны для асинхронных приложений MVVM: команды https://msdn.microsoft.com/en-us/magazine/dn630647.aspx

Они должны охватывать хорошую стратегию, особенно при работе с async / await.

person silverfighter    schedule 11.02.2016
comment
Я читал об async / await и TPL и обязательно посмотрю эти статьи. До сих пор я придерживался Background Worker, потому что это казалось довольно простым в реализации. - person Fang; 11.02.2016