How should I poll a db table for a 'live' view in an MVVM scenario?

c#, design-patterns, mvvm, wpf

Solution

It's true your `ViewModel` shouldn't do any work that is the responsibility of "the model", in this case the data access.

There's really a bunch of ways you can achieve this but I'd implement a `repository pattern` around your database related logic. Then inject the `repository` dependency into your `ViewModel`.

How these two fellas communicate is yet another story. You could just ask for new data from time to time, using `Task.Run()` or similar that is not blocking the UI.

Or if you like you could make a separate "repository poller" that notifies the `ViewModel` when data has been queried, via `events`, `pub/sub` and what-not.

So the minimal mock `repository`, passing around plain `string`s, could look something like this.

// repository 
public interface IRepository<T>
{
    Task<IEnumerable<T>> GetAll();
}

// repository impl
public class SimpleRepository : IRepository<string>
{
    private readonly IList<string> _items = new List<string>();

    public SimpleRepository()
    {}

    public Task<IEnumerable<string>> GetAll()
    {
        if (_items.Count > 10)
            _items.Clear();
        _items.Add(string.Format("string{0}", _items.Count));

        Thread.Sleep(250); // queries take some time...
        return Task.FromResult((IEnumerable<string>) _items);
    }
}

For examples sake there's just `GetAll()` method returning all the "records" from the repo.

Now you related `ViewModel` could use this repo in the following manner.

// ViewModelBase just implements the INotifyPropertyChanged
public class MainViewViewModel : ViewModelBase
{
    private ObservableCollection<string> _items;

    public MainViewViewModel()
        : this(new SimpleRepository())
    {}

    // pass in the repository dependency
    public MainViewViewModel(IRepository<string> simpleRepository)
    {
        SimpleRepository = simpleRepository;
        Task.Run(async () =>
            {
                // sophisticated polling logic here
                while (true)
                {
                    // update collection
                    var results = await SimpleRepository.GetAll();
                    Items = new ObservableCollection<string>(results);
                    Thread.Sleep(250);
                }
            });
    }

    public IRepository<string> SimpleRepository { get; set; }

    public ObservableCollection<string> Items
    {
        get { return _items; }
        set { _items = value; OnPropertyChanged();}
    }
}

So this would update the `ObservableCollection<T>` from time to time and now the logic is out of the `ViewModel`. Related `XAML` below if you are keen to test it out.

<Window x:Class="WpfApplication1.View.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:viewModel="clr-namespace:WpfApplication1.ViewModel"
        Title="MainWindow"
        Height="300"
        Width="250">
    <Window.DataContext>
        <viewModel:MainViewViewModel />
    </Window.DataContext>

    <Grid Margin="10">
        <ItemsControl ItemsSource="{Binding Items}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding}"></TextBlock>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>

And it sure looks, feels and behaves pro-fessional! :)

If you'd like the polling responsibility out of `ViewModel`, too, then your `RepositoryPoller` could manifest itself as

// generic poller
public class RepositoryPoller<T>
{
    public event EventHandler<RepositoryEventArgs<T>> OnQueryComplete;
    private readonly System.Timers.Timer _timer;
    private TimeSpan _timeSpan;

    // wire-up poll timer
    public RepositoryPoller()
    {
        _timer = new System.Timers.Timer();
        _timer.Elapsed += (sender, args) => Query();
    }

    // provide poll interval and repository, or set via properties
    public RepositoryPoller(TimeSpan timeSpan, IRepository<T> repository)
        :this()
    {
        TimeSpan = timeSpan;
        Repository = repository;
    }

    public TimeSpan TimeSpan
    {
        get { return _timeSpan; }
        set { _timeSpan = value; _timer.Interval = _timeSpan.TotalMilliseconds; }
    }

    public IRepository<T> Repository { get; set; }

    public void Start()
    {
        if (TimeSpan.TotalMilliseconds > 0)
            _timer.Start();
    }

    public void Stop()
    {
        _timer.Stop();
    }

    // query for data
    private async void Query()
    {
        var results = (await Repository.GetAll()).ToArray();
        RaiseQueryCompleted(results);
        NotifyQueryCompleted(results);
    }

    // send results as event
    private void RaiseQueryCompleted(IEnumerable<T> results)
    {
        var handler = OnQueryComplete;
        if (handler != null)
            handler(this, new RepositoryEventArgs<T>(results));
    }

    // send results as message
    private void NotifyQueryCompleted(IEnumerable<T> results)
    {
        Messenger.Default.Send(new GenericMessage<IEnumerable<T>>(this, results));
    }
}

// event args holding queried items
public class RepositoryEventArgs<T> : EventArgs
{
    public RepositoryEventArgs(IEnumerable<T> result)
    {
        Results = result;
    }

    public IEnumerable<T> Results { get; set; }
}

So poller queries the data between given intervals and notifies the listeners via good old `event` and then more loosely coupled `message` (which uses MVVMLight Libraries NuGet dependency).

In your `ViewModel` you'd use it like so

public MainViewViewModel()
    : this(new RepositoryPoller<string>(
          TimeSpan.FromSeconds(0.5d), new SimpleRepository()))
{}

public MainViewViewModel(RepositoryPoller<string> repositoryPoller)
{
    RepositoryPoller = repositoryPoller;

    // If you prefer pub/sub...
    Messenger.Default.Register<GenericMessage<IEnumerable<string>>>(this, message =>
        {
            var results = message.Content;
            Items = new ObservableCollection<string>(results);
        });

    // Or in case events feel more liek home
    RepositoryPoller.OnQueryComplete += (sender, args) =>
        {
            var results = args.Results;
            Items = new ObservableCollection<string>(results);
        };
    RepositoryPoller.Start();
}

public RepositoryPoller<string> RepositoryPoller { get; set; }

Hope it gave you some ideas.

Problem

I have a log db table that I'd like to display in a `GridView`. I already have a view model that polls the db and updates the whole `ObservableCollection` if the table has changed. I do nothing else with this data, so I thought a further back model would be unnecessary. However, now I'm starting to think it's improper that the viewmodel does any work with anything but the view and the model, and that I should introduce a model to observe the database and provide the viewmodel with any changes. The updated `ObservableCollection` in the view model then communicates the new data to the `DataGrid` in the view.

Original source