Fang
Fang

Reputation: 2427

Notify ViewModel of Asynchronous changes in Model

I'm relatively new to MVVM and I'm wondering about the best way to structure my application. Here is a sample of my models:

  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 implements INotifyPropertyChanged).

Here is my corresponding 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);
    } 

Calling ModelSource.getDatabasesAsync(_currentConfig) gets my SQL Data in my model. Due to some of my SQL queries being quite complex, I've implemented a Background Worker that runs these queries asynchronously.

How do I get the data into my ViewModel, which is bound to my View? Or is my design approach as a whole faulty?

Things I've considered and tried:

Upvotes: 1

Views: 511

Answers (2)

silverfighter
silverfighter

Reputation: 6882

Have you seen these articles?

Async Programming : Patterns for Asynchronous MVVM Applications: Data Binding https://msdn.microsoft.com/en-us/magazine/dn605875.aspx

Async Programming : Patterns for Asynchronous MVVM Applications: Commands https://msdn.microsoft.com/en-us/magazine/dn630647.aspx

These should cover a good strategy especially when working with async/await.

Upvotes: 1

Yarik
Yarik

Reputation: 1578

  1. Move all your database logic into a service (aka repository) class.
  2. It is OK to bind directly to the model properties instead of creating a dozen ViewModel proxy classes for each Model, as soon as you don't need any special view-related logic around a particular model. So exposing a collection of PCDatabase is OK.

Since you're using BackgroundWorker, I assume you use .NET Framework 3.5 and don't have 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;
        });
    }

Upvotes: 2

Related Questions