Freigeben über


Collection Binding Options in WCF RIA Services SP1

One of the most common tasks when developing a RIA Services application binding to a collection of data. There are some declarative options that include binding directly to your DomainContext or DomainDataSource. These options are great for spinning up applications quickly, but don’t translate well to view model patterns. For that reason, I thought it’d be worth taking a moment to go through the options that make sense in an MVVM context.

Bind to an EntitySet

As of SP1, we improved binding support for EntitySets. EntitySets are aggregating, un-ordered collections. That means every time an entity is loaded by your DomainContext it will be added to its corresponding EntitySet. Binding to an EntitySet will give you a great view of every entity of a specific type your DomainContext knows about. Adding to or removing from an EntitySet will result in calls to the insert and delete operations being invoked when you submit changes to your DomainService.

Making an EntitySet available in your view model would look like this.

   public EntitySet<SampleEntity> EntitySet
  {
    get { return this.Context.SampleEntities; }
  }

Bind to an EntityCollection

In SP1, we also improved binding support for EntityCollections. EntityCollections are collections of entities associated with a specific Entity. In a manner similar to the EntitySet, an EntityCollection will be updated as new entities are loaded into your DomainContext. Each will be added to the EntityCollection of the Entity it is associated with. Adding and removing from an EntityCollection will only update the association. Most of the time, this results in an update operation being invoked when you submit changes.

Since EntityCollections are tied to specified Entities, having a property on your view model seems like an uncommon case. Nevertheless, making an EntityCollection available in your view model would look like this.

   public EntityCollection<SampleEntity> EntityCollection
  {
    get { return this.CurrentEntity.AssociatedEntities; }
  }

Bind to an EntityList

EntityList is a type new to SP1 and can be found in Microsoft.Windows.Data.DomainServices in the Toolkit. An EntityList is an observable collection backed by an EntitySet. The Source property on the EntityList defines the entities it contains, and it will not update as new entities are loaded into your DomainContext. It will update if entities it contains are deleted from the EntitySet or when entities are added or removed from its Source collection. Adding to an EntityList will behave in one of two ways. If the entity already exists in the backing EntitySet, then it is just added to the list. If it does not exist, then it will be added to the EntitySet as well. Removing from the EntityList will always remove the entity from the backing EntitySet. Binding to an EntityList is a great option when you only want to see a subset of the entities your DomainContext knows about.

Making an EntityList available in your view model would look like this.

   private EntityList<SampleEntity> _entityList;
  public EntityList<SampleEntity> EntityList
  {
    get
    {
      if (this._entityList == null)
      {
        this._entityList = new EntityList<SampleEntity>(
          this.Context.SampleEntities);
      }
      return this._entityList;
    }
  }

  private void LoadSampleEntities()
  {
    this.EntityList.Source =
      this.Context.Load(this.Context.GetAllEntitiesQuery()).Entities;
  }

Bind to an ICollectionView

In case you’re not familiar with the ICollectionView interface, it’s used by a number of components (DataGrid, DataForm, etc.) as a view over a source collection. For example, when you bind DataGrid.ItemsSource to a collection, it will create a collection view behind the scenes to interact with. Instead of just having the control instantiate a view behind the scenes, you have the option of making the ICollectionView available for binding directly from your view model.

It may not be readily apparent why you’d do this, but as you did into the interface, you’ll noticed it has a number of properties for defining how the view presents the source collection. You can control the sorting, grouping, and filtering as well as which item is selected; all from the ICollectionView.

In Silverlight, most collection view implementations are internal. Fortunately, there’s an easy way of letting the framework instantiate the correct view for the collection that uses the CollectionViewSource.

   private static ICollectionView CreateView(IEnumerable source)
  {
    CollectionViewSource cvs = new CollectionViewSource();
    cvs.Source = source;
    return cvs.View;
  }

Making an ICollectionView available in your view model would look like this.

   private ICollectionView _collectionView;
  public ICollectionView CollectionView
  {
    get
    {
      if (this._collectionView == null)
      {
        this._collectionView = CreateView(this.Context.SampleEntities);
      }
      return this._collectionView;
    }
  }

The downside of using this approach is that all the existing collection view implementations only operate over the in-memory collection. That means you have to pull all the data to Silverlight before you apply sorting or filtering.

Bind to a DomainCollectionView

To address the limitations of existing ICollectionView implementations we’ve added the DomainCollectionView in SP1 in Microsoft.Windows.Data.DomainServices in the Toolkit. It is designed to allow for asynchronous sorting, grouping, and paging, and works with all the controls that use the collection view interfaces. This enables you to page over data on your server using the core data controls.

The setup for a DomainCollectionView is slightly more complicated than some of these other options. This is because it requires load callbacks as well as a source collection. These callbacks can be implemented just like any other load operation in your view model.

   private LoadOperation<SampleEntity> LoadSampleEntities()
  {
    return this.Context.Load(
      this.Context.GetAllEntitiesQuery().SortPageAndCount(
        this.DomainCollectionView));
  }

  private void OnLoadSampleEntitiesCompleted(LoadOperation<SampleEntity> op)
  {
    if (op.HasError)
    {
      // TODO: handle errors
      op.MarkErrorAsHandled();
    }
    else if (!op.IsCanceled)
    {
      this.EntityList.Source = op.Entities;

      if (op.TotalEntityCount != -1)
      {
        this.DomainCollectionView.SetTotalItemCount(op.TotalEntityCount);
      }
    }
  }

With callbacks defined, making a DomainCollectionView available in your view model would look like this.

   private DomainCollectionView<SampleEntity> _domainCollectionView;
  public DomainCollectionView<SampleEntity> DomainCollectionView
  {
    get
    {
      if (this._domainCollectionView == null)
      {
        this._domainCollectionView =
          new DomainCollectionView<SampleEntity>(
            new DomainCollectionViewLoader<SampleEntity>(
              this.LoadSampleEntities,
              this.OnLoadSampleEntitiesCompleted),
            this.EntityList);
      }
      return this._domainCollectionView;
    }
  }

This is just enough to get you started with the DomainCollectionView, but it leaves out most of the details. In this next post, I go much more in-depth and explain the design and core API.

https://blogs.msdn.com/b/kylemc/archive/2010/12/02/introducing-an-mvvm-friendly-domaindatasource-the-domaincollectionview.aspx

Are there other options?

There are always other options, but I’m not sure I’d recommend using them. One common alternative would be to make the property types of these collections their respective interfaces (for instance IEnumerable or ICollectionView). This can reduce coupling and give you more flexibility. Also, since most Silverlight controls that bind to collections are just looking for interfaces, the impact is negligible.

If you have other suggestions, I’d love to hear them. Hope this overview helps.

Comments

  • Anonymous
    December 02, 2010
    What about ObservableCollection? How will that behave compared to the options mentioned in your blog? For collections which will not change and no sorting and filtering is needed, IEnumerable is just fine, I guess?

  • Anonymous
    December 02, 2010
    @Jaap The real issue here is how the DataGrid (etc.) interprets your collection. If you use an ObservableCollection, the Add and Remove features will be available but won't work correctly. As long as you understand the limitation, you could use it, but that caveat makes me think using an ObservableCollection is an anti-pattern. For read-only scenarios, you can use pretty much any IEnumerable. You just need to make sure to take care of turning off editing, adding, etc. You can support an in-memory view (sorting, etc.) over read-only data, but for that scenario, you should try to use a read-only collection (an array, ReadOnlyObservableCollection, etc).

  • Anonymous
    December 05, 2010
    You said that Add and Remove feature are not working correctly with ObservableCollection. I expect you mean that added or removed entities are not added or removed in the domaincontext as somebody perhaps would expect? That's clear for me. I was thinking more about a non WCF RIA services related list of items. In that case, would recommend ObservableCollection or the ICollectionView approach?

  • Anonymous
    December 06, 2010
    Yes, that's what I mean. All these options are with respect to entities (though the final two are not exclusive to entities). For non-entity item binding, I prefer an ObservableCollection.

  • Anonymous
    December 06, 2010
    Thanks for the update, this will simplify the MVVM/RIA combination a lot. Personally, I've been very fond of using PagedCollectionView in my ViewModel, even with some of the  limitations (e.g. lack of DataForm Add/Remove, and general Drag-drop support...). One of the most useful features of the PCV is the (PagedCollectionView).Filter property. Using the .Filter option, it's very easy to apply (and re-apply) complex filters/predicates in the ViewModel with a simple PCV.Refresh() operation. Are you planning to add the .Filter property for the DomainCollectionView as well? LarsM

  • Anonymous
    December 06, 2010
    @LarsM The DCV should already support the Filter property. It may be inconvenient to use if you have to call refresh, though. Give it a try and let me know. Filtering in general is a little more interesting. In the RIA/DomainDataSource world, filters are defined and serialized a little differently (as LINQ .Where clauses). These queries get executed on the server and not the client. I assume that'd be the preferred approach for the DCV too. Is there a need for first-class client-side filtering? You can see where this fell on my breakdown in this post blogs.msdn.com/.../my-thoughts-on-an-mvvm-friendly-domaindatasource.aspx.

  • Anonymous
    December 07, 2010
    In your DCV example, what is the role of this.EntityList? In the EntityList example, LoadSampleEntities populated it. -- Does the DCV populate this behind the scenes? Does this have to be an EntityList? I hate having to ask so many questions :( lol ... I'll give a little overview: My application requires a windowed (MDI) environment (adapted ChildWindow implementation). There will be one window to display a list of MyItems. Via a menu outside the scope of this window, the user can add a new item to the  Context.MyItems. This item is popped into it's own new window. Likewise, editing an item from the list of items opens a new window. Am I overlooking a way to keep the list of items synchronized with the live Context.MyItems? For instance: Suppose a user adds a new item to the Context.MyItems (but does not save). Will this item show up in the grid automatically, after a user refresh, or even at all (prior to save)? (Maybe the question is: Do I really want the list to be synced like that?) (That visual stuff probably doesn't matter a whole lot. Well, except for context around keeping things synced & one small issue: saving an individual item. For this I'm planning to use EntitySet Import/Export extensions in RIA Services Contrib. It's either that, or a new Context for every window. I can't decide which one is worth the effort, or might incur unexpected problems.)

  • Anonymous
    December 07, 2010
    wow. Not sure how I missed that, but it's obvious where this.EntitySet.Source is set. ;-) actually, it seems after re-reading your post (again), a lot of what I asked is explained. Like if an Item is added to the backing EntitySet, it will get added to the EntityList (and as a result, show up in a grid)

  • Anonymous
    December 07, 2010
    @felonious That's the only direction it doesn't work :). Since the EntityList contains a subset of the entities in EntitySet, it will not pick up items added to the EntitySet. If you add to the EntityList, they will show up in the EntitySet, but not the other way around. In the sample, EntityList is the 'Source'. Take a look at my longer DCV post for more context. You can keep pages in sync by passing around a reference to the collection. Some MVVM implementations use DI for this kind of thing, but there are many different wasys to do it.

  • Anonymous
    December 07, 2010
    doh! that makes sense. I think it's safe to say that use-case is defeated: if the user wants to see his new item in the grid, he must save it, then refresh the grid contents from the server. Now this might seem obvious (but since i'm over-thinking..): If the user edits an item which is currently displayed in the grid (in one of those non-child windows), the grid will instantly reflect those changes, right? (assumes referential equality of op.Entities[itemKey] & Context.Entities[itemKey])

  • Anonymous
    December 07, 2010
    Yes. Assuming referential equality, changes to an entity will immediately appear in every place where a control is bound to that entity. Also (just to reiterate for anyone following this conversation later), you'll need to make sure you're using the same DomainContext in each place. Identical entities in separate DomainContexts are not referentially equivalent.

  • Anonymous
    December 07, 2010
    awesome. sometimes clearing the cobwebs from all the disparate demos and samples over long periods of time is a rough ride :-x

  • Anonymous
    December 10, 2010
    Hi Kyle, I am with Lars above - I use the PCV almost exclusively.  The important features being Sorting and Filtering on the client.  Maybe I am misguided, but I prefer to bring a large set of data down to the Client and keep it there, sorting and filtering as needed.  While there may be a short delay to bring the data down the first time for the user, after that the data is lightening fast.  For example, I have an Email Client that brings down 200 emails at once for the user, instead of one by one.  But then I need to sort and filter the emails by a variety of data. I am going to try switching over to do everything on the server as a test, maybe Ria Services is fast enough - I am just tring to get away from the notable latency in many internet apps. For example, I would like the new DomainCollectionView to keep the data once it has been downloaded.  I don't understand why it would automatically discard it just because the user goes to another page.  Getting a page of data is a slow operation.  What if the user want to go right back to the prior page.  He then must wait again.  Thus, an option to save the local data when paging would be great. I am no expert in these matter - I just know what has worked for me so far. Greg

  • Anonymous
    December 10, 2010
    I suppose there can be a place for the PCV too. :) If you're bringing down 200 emails, it's not that big a deal, but if you're bringing down 20,000 emails it'd be an entirely different story. Neither the DCV or PCV is going to be right for every situation, so use your best judgement when picking either. The thing about the DCV is it doesn't necessarily need to discard any of the data you've loaded. The solutions I've shown here an elsewhere do for simplicity sake. However, you could write a custom CollectionViewLoader that stored loaded data, loaded in blocks of multiple pages, or preloaded pages in the background for a better experience. The point is the loading, caching, and paging policies are now fully your responsibility. It's a little more work, but a lot more control.

  • Anonymous
    December 11, 2010
    Good discussion. This is not necessarily a discussion about lazy or eager loading, but also about the complexity of the local filtering. In many cases, predicates/filters are not only based on one source/dataset, but may be combinations of many different datasets. Use case: A result collection is built by moving selected elements from two or more source collections (List, Datagrid, TreeView). After moving an element to the result collection, the original elements should no longer be available in the source collection. This really is a ViewModel only operation, since the combined dataset stays the same, and no re-loading (or persistence) should be performed until all moves are made. If you use local Filters that combines the content of each collection, it's very easy to keep the content in sync between source and result collections. If you had to persist and reload (with query filters) all the relevant datasets every time an element is moved, it would cause a lot of unnecessary database traffic.

  • Anonymous
    December 22, 2010
    I am not so sure on PagedCollectionView anymore. The ICollectionView returned from the CollectionViewSource seems to work pretty well and doesn't have the extra weight of interfaces that don't work like the PCV has.

  • Anonymous
    December 28, 2010
    My existing code is using a lot of PCVs.  I guess in theory I'm fine with replacing all of those with ICollectionViews, but what are the benefits?  When I use code similar your CreateView, I get a ListCollectionView instance when using an EntitySet as the source.  It's just as weakly-typed as the PCV, so I don't get any benefit there.  It doesn't implement IPagedCollectionView, so there's presumably some performance benefit from not having to do that.  However, how substantial is it?  Is it enough to make up for the overhead of instantiating a CollectionViewSource and using it's associated plumbing? Also, what is the benefit of instantiating a CollectionViewSource to have it cast an EntitySet to ICollectionViewFactory and call CreateView as opposed to doing it myself?

  • Anonymous
    January 04, 2011
    @Shawn I don't think there's a whole lot of overhead tied to the CollectionViewSource. I can't guarantee it, but the path through the source seems fairly straightforward. On the other hand, you may not see much of a performance difference between the two collection view implementations either. I am not aware of any relative performance comparisons. The CVS approach works for all collections where the ICVF approach will only work for EntitySets (and EntityCollections). I use it for that reason, but otherwise you can feel free to call CreateView yourself.

  • Anonymous
    January 15, 2011
    Can you qualify the statement 'Most of the time, this results in an update operation being invoked when you submit changes.' under your description of Binding to An EntityCollection?

  • Anonymous
    January 16, 2011
    The comment has been removed

  • Anonymous
    February 06, 2011
    For my application PagedCollectionView on top of EntityList is the best fit. But I am bothered by the fact that the EntityList won't pick up new records added directly to the data context; I like the idea of the data context having the most complete picture of the "truth" and letting it drive the UI. I had hoped to subclass EntityList so I could keep it in sync with the underlying EntitySet but EntityList is sealed. Bummer! Any reason for this? So I built this simple wrapper:        class AllEntityList<T> where T : Entity {            readonly EntityList<T> list;            public AllEntityList(EntitySet<T> set) {                list = new EntityList<T>(set, set.ToArray());                set.EntityAdded += (sender, args) => { EntityList.Add(args.Entity); };            }            public EntityList<T> EntityList { get { return this.list; } } // Bind PagedCollectionView to this        } Do you see any problem with this approach? I did a quick test adding and removing entities from the view and data context and it seemed to work. I didn't test (or understand) the difference between Remove and Detach, so maybe that will cause a problem. I also didn't test adding/removing entities with foreign key associations either. Here is some background. I want to bind a grid to an ICollectionView, since sort, filter, and group are useful. I also want ICollectionView.CanAdd==true so the user can add new records inside the grid he/she is using for edit and delete; this requirement rules out binding directly to the EntitySet. I also plan to download all the data the user needs and work offline most of the time, probably through a shared data context. Periodically I will connect to the server and refresh the data context, and this might cause new entities to be loaded; obviously I'd like these entities to appear in the UI and this is where the EntityList falls short. There are also times when I plan to add records programatically to the underlying data context (rather than through the view) and I'd also like these records to appear in the UI. I'm a bit confused why there isn't a binding option that reflects the complete data context AND permits an ICollectionView where CanAdd is true.

  • Anonymous
    February 07, 2011
    @Justin I'd recommend binding to EntitySet as it implements the aggregating behavior you want. Also, the binding behavior has changed in SP1 to allow adding and deleting through a view (blogs.msdn.com/.../improved-binding-support-in-entityset-and-entitycollection.aspx). If you want to access the view directly, you can cast the EntitySet to an ICollectionViewFactory and use the CreateView method.

  • Anonymous
    February 07, 2011
    I thought I had SP1 Beta installed but I didn't. Having more success now but still a bit confused. If I do something like this... var view = new PagedCollectionView(context.PeopleEntitySet); ...then the resulting view has CanAdd set to false. Not sure why. If I do as you suggest... var view = (context.PeopleEntitySet as ICollectionViewFactory).CreateView(); ...then the view is just an ICollectionView without editing facilities. To edit, I need to cast it to an IEditableCollectionView. It is a ListCollectionView, which is some kind of internal class that I can't expose in my view model. I'd like to expose a fully functional view object in my view model - one that supports both ICollectionView and IEditableCollectionView - but can't do this easily. I can expose the ICollectionView but need to remember to cast it to IEditableCollectionView whenever I need to do editing stuff.

  • Anonymous
    February 08, 2011
    @Justin The view created in the second option implements ICV and IECV and will support adding just fine at runtime. It's a little unfortunate it's not as useful at compile time. If you want a nicer type to work with, you could take a look at the CollectionViewWrapper type shipped in the latest version of the RIA Services Toolkit. Feel free to contact me (via the 'Email Blog Author' link above) if you want to know how to use it.

  • Anonymous
    February 08, 2011
    I want to implement MVVM in vb.net .        Private Function LoadData() As LoadOperation(Of tbFXAS_AllocationHeader)            Return dc.Load(dc.GetTbFXAS_AllocationHeaderQuery())        End Function        Public Property Data() As ICollectionView            Set(ByVal value As ICollectionView)                Me._view = value            End Set            Get                Return Me._view            End Get        End Property        Sub AddItemsToData(ByVal p1 As String)            Me._source = New ObservableCollection(Of tbFXAS_AllocationHeader)(Me.dc.tbFXAS_AllocationHeaders)            Me._Loader = New DomainCollectionViewLoader(Of tbFXAS_AllocationHeader)(AddressOf LoadData)            Me._view = New DomainCollectionView(Of tbFXAS_AllocationHeader)(Me._Loader, Me._source)            Me.Data.DeferRefresh()        End Sub in the domaincollectionViewloader : there are just one parameter ( the address of the loadoperation ) , What the wrong please , there are no parameter for loadingcompleted . I don't see any data loaded to the GridView , the GridView is binded to "Data" Property

  • Anonymous
    February 13, 2011
    I'm sorry if I'm a little late to this post. Do you know how I might go about wrapping my entities in a ViewModel class using one of the methods you describe in this article? For example, I would like to extend the functionality of SampleEntity by wrapping it in a SampleEntityViewModel that contains all the commands and properties to support the manipulation of that object by the View. My problem is that I have a pretty deeply nested model. My SampleEntity class has a collection SampleEntityLineItem children. Those may also have children. How do I get them all wrapped in ViewModels?

  • Anonymous
    February 14, 2011
    The comment has been removed

  • Anonymous
    April 07, 2011
    ...but as you DIG into the interface...

  • Anonymous
    May 11, 2011
    The comment has been removed

  • Anonymous
    May 11, 2011
    @duluca I'm not sure the answer. The DataForm is rather unfriendly when it comes to edit transactions. I ran into issues with it in this post (blogs.msdn.com/.../improved-binding-support-in-entityset-and-entitycollection.aspx). It seems to me like DataForm.CancelEdit would be exactly what you want. I'm not sure why it isn't working.

  • Anonymous
    May 11, 2011
    @Kyle Good to know that DataForm is unfriendly. I'll be hostile towards it from now on :) I had initially planned to have a non shared view model, but a bug in MEFedMVVM pushed me down this road. Well, after wasting a lot of time on this, I've resolved my issue by fixing the bug with [PartCreationPolicy(CreationPolicy.NonShared)]. So now the dataform lives and dies with its own view model. When creating new items, I open it up in a new window so when I hit cancel, it all goes away without compliant.