May
29
2010

Advanced Paged Collection View

In the previous post I showed some of the ways that collections of entities can be exposed from your ViewModel. The load examples I showed was relatively simple as it demonstrated a simple load with no ordering or filtering and there was only a single load. In a more realistic example where ordering or filtering is being used and the EntitySet cannot be cleared between loads then only the PagedCollectionView and ObservableCollection will work. In this post, I will show a more full example of how you can combine WCF RIA Services, ViewModel, and PagedCollectionView to fully support filtering, sorting, and multiple loads.

private PagedCollectionView _customers; 
public PagedCollectionView Customers
{ 
    get
    {
        if (_customers == null)
        {
            _customers = new PagedCollectionView(Context.Customers);
            _customers.SortDescriptions.Add(new SortDescription("Name", ListSortDescription.Ascending));

         }
        return _customers;
    }
}

public void LoadCustomersByState(string state)
{
    var qry = this.Context.GetCustomersQuery().Where(c=>c.State == State).OrderBy(c=>c.Name);
    LoadOperation loadOp = this._customerContext.Load(qry);
    Customers.Filter = c=>((Customer)c).State == state;
}

When the PagedCollectionView was created, a SortDescription was set, this ensures that the PagedCollectionView will always be correctly sorted. The load is not clearing the EntitySet so it is possible for customers from multiple states to be loaded at the same time but the filter on the PagedCollectionView makes sure that only the currently selected state’s customers is being shown.

It is possible to get the same effect using the ObservableCollection but it requires refilling the ObservableCollection every time an entity is updated or inserted into the EntitySet which can cause problems for the bound UI controls.

 

The above example shows how to keep the load in sync with the PagedCollectionView, here is a second scenario where the load is separated from what is being shown in the UI:

private PagedCollectionView _customers; 
public PagedCollectionView Customers
{ 
    get
    {
        if (_customers == null)
        {
            _customers = new PagedCollectionView(Context.Customers);
            _customers.SortDescriptions.Add(new SortDescription("Name", ListSortDescription.Ascending));
            _customers.Filter = c=> (string.IsNullEmpty(SelectedState) || ((Customer)c).State == SelectedState);
         }
        return _customers;
    }
}

private string _selectedState = null;
public string SelectedState
{
    get {return _selectedState;}
    set
    {
        _selectedState = value;
        Customers.Refresh(); //this lets the PCV know that the filter changed
        RaisePropertyChanged(“SelectedState”);
    }

public void LoadCustomers()
{
    var qry = this.Context.GetCustomersQuery().OrderBy(c=>c.Name);
    LoadOperation loadOp = this._customerContext.Load(qry);
}

 

Wrap Up

The most important thing that the PagedCollectionView gains you is a clear separation of concerns within the ViewModel. Any business logic code adding, deleting, or updating entities does need to worry about updating the UI, that is taken care of by the PagedCollectionView which is monitoring the EntitySet itself for those changes. The only thing that the PagedCollectionView cannot detect for itself is when the any external values used in the Filter are modified so you do need to keep track of that and call the Refresh method whenever that happens. Other then that, in the ViewModels that the above code would be a part of no other reference to the PagedCollectionView properties would be necessary. The same can not be said for any of the other options that are available. In the last part of this series I will be discussing what is coming in the future.

May
29
2010

View Model Collection Properties for WCF RIA Services

For the most part combining WCF RIA Services with the MVVM pattern is easy. One area of difficulty is in exposing collections of entities from the ViewModel. This post is going to show some of the current possibilities including my favorite solution, the PagedCollectionView. In two follow up posts I will be discussing some more advanced PagedCollectionView examples and then some future possibilities.

Using LoadOperation.Entities

private IEnumerable<Customer> _customers; 
public IEnumerable<Customer> Customers
{ 
    get {return _customers;}
    private set
    {
        _customers = Customers;
        RaisePropertyChanged("Customers");
    }
}

public void LoadCustomers()
{
    LoadOperation loadOp = this.Context.Load(Context.GetCustomersQuery());
    Customers = loadOp.Entities;
}

Pros

  • Simple, easy to understand.
  • Matches example code from RIA Services documentation.

Cons

  • LoadOperation.Entities is read only, so this is only suitable for read only applications.

Using EntitySet

public IEnumerable<Customer> Customers
{ 
    get {return Context.Customers;}
}

Pros

  • Simple
  • EntitySet does implement INotifyCollectionChanged so as it is updated the UI will be automatically refreshed.

Cons

  • Exposing the EntitySet to the view, even hidden behind IEnumerable, gives the view too much access.
  • Binding to the EntitySet means that there is no ability to filter, everything in the EntitySet will be bound to the UI.
  • The built in add/remove abilities of the DataGrid and DataForm do not support the EntitySet

Using ObservableCollection

private ObservableCollection<Customer> _customers; 
public ObservableCollection<Customer> Customers
{ 
    get {return _customers;}
    private set
    {
        _customers = Customers;
        RaisePropertyChanged("Customers");
    }
}

public void LoadCustomers()
{
    this.Context.Load(Context.GetCustomersQuery(), LoadCustomersCompleted, null);
}

private void LoadCustomersCompleted(LoadOperation<Customer> lo)
{
    Customers = new ObservableCollection<Order>(Context.Customers);
}

Pros

  • ObservableCollection is not read only so this code is not limited to read only applications.
  • ObservableCollection does implement INotifyCollectionChanged so as it is updated the UI will be automatically refreshed.

Cons

  • Every insert, delete, clear, detach, attach, and load within the ViewModel has to manually keep the ObservableCollection in synch. This makes it easy to get into situations where the ObservableCollection gets out of synch with the DomainContext. The worst case scenario here is that a deleted entity gets re-added as a new entity accidently because it hadn’t been removed from the ObservableCollection.
  • ObservableCollection implements IList. This means that the built in add and remove for both the DataGrid and DataForm will be enabled even though any entities added or removed directly to the ObservableCollection will not be added or removed from the EntitySet.

Using an ICollectionView

What is ICollectionView?

Marlon Grech has a good primer on the ICollectionView, I suggest you go and read his entry on them and then come back.

Have a good read? Great, so now you know what ICollectionView does. Unfortunately, the classes demonstrated by Marlon are marked as internal in Silverlight so we can’t use them. Fortunately, we did get a consolation prize back in Silverlight 3: PagedCollectionView.

Using PagedCollectionView (PCV)

private PagedCollectionView _customers; 
public PagedCollectionView Customers
{ 
    get
    {
        if (_customers == null)
            _customers = new PagedCollectionView(Context.Customers);
        return _customers;
    }
}

public void LoadCustomers()
{
    LoadOperation loadOp = this.Context.Load(Context.GetCustomersQuery());
}

Pros

  • PCV automatically refreshes based on the contents of the EntitySet.
  • PCV has full sorting and filtering ability. This will be demonstrated in the second post of this series.

Cons

  • The PCV’s implementations of IEditableCollectionView and IPagedCollectionView are not compatible with the EntitySet. However, the incompatibility is reported by the PCV so the built in add/remove abilities of the DataForm and DataGrid are properly disabled.
  • The PCV is not strongly typed limiting its usefulness inside the ViewModel.
  • ICollectionView includes a “CurrentItem” property which is used by bound list controls. For example, if two comboboxes are sharing the same PagedCollectionView for the ItemsSource then changing the selection in one combobox will cause the selection to change in the second combobox as well.

Conclusion

None of the above options are perfect, but the PagedCollectionView is certainly the best option that we currently have. In my next post I will be discussing the PagedCollectionView in more depth and in the third post I will be discussing what the optimal solution would be.

Month List