Note: The limitations discussed herein no longer apply once you get WCF RIA Services V1 SP1 Beta (or later). See Kyle McClellan's blog for more info.

Tom Beeby posted a topic on the RIA Services forums about adding a new entity via the DataForm buttons.  Specifically, he pointed out a problem where the DataForm does not light up the Add/Remove buttons when bound to an EntityList from your DomainContext.  To exacerbate the problem, it turns out that binding to an EntityCollection<T> from RIA Services won’t let you Add or Remove either.  I’ve dug into this problem with some team members, and we’ve identified the cause of the problem.

To be clear, binding a DataForm and getting Add/Remove support works just fine when you’re binding to the DomainDataSource’s Data property.  It’s only when you’re binding directly to the DomainContext that the buttons don’t light up.

It turns out that there’s nothing built into RIA Services to easily work around this problem right now.  And we haven’t yet decided if/how we’ll address the issue in later releases.  But I came up with a solution that people could use today.  It comes with no warranties, no guarantees that it won’t kill your cat or cause you other sorts of pain.  But from my limited testing, it seems to work for me.

If you want to skip the details, you can grab the solution source and binaries here – there’s another link at the bottom of the post.

When the DataForm is bound to a collection, it inspects the collection to see if it implements an interface that would support adding and removing items.  If your collection implements IList, I’ve been told that the Add and Remove buttons will light up.  Neither EntityList<T> nor EntityCollection<T> implement this interface. It wouldn’t make sense either, since EntityList and EntityCollection don’t really work in an index-oriented fashion and IList is index-centric.  Fortunately, we have another option, and it’s what the DomainDataSource leverages.  We can implement ICollectionView and IEditableCollectionView.

DataForm will check to see if the collection implements ICollectionView.  If so, it will further check to see if the collection implements IEditableCollectionView.  If it does, then your collection will be queried for CanAdd and CanRemove, and the interface will be used to drive the respective functions.  To get DataForm to light up the Add and Remove buttons for your EntityList or EntityCollection, we just need to provide a collection that implements ICollectionView and IEditableCollectionView.

Okay, I lied, that’s not entirely true.  We also need to implement IEnumerable, INotifyCollectionChanged, and INotifyPropertyChanged.  Using my favorite Visual Studio editor shortcut, Ctrl+(dot), Enter, we can very quickly define our API and start filling in the methods and properties.  But of course, we do have a slight problem: I wanted to provide a solution that works against the already-released July 2009 Preview of RIA Services, so we can’t just add this implementation into RIA Services.  So instead, I’m going to provide a way to bolt this on.  This requires a little binding trickery.

The idea is to create a wrapper class that will take either an EntityList<T> or an EntityCollection<T>, and provide the right interface implementations on top of what it’s given.  Then, we’ll create a binding converter that will take the EntityList<T> or the EntityCollection<T>, and return a wrapper collection instance.  We’ll call our wrapper collection an EntityCollectionView<T>, and our converter an EntityCollectionViewConverter.

A mere 500 lines of code into the exercise (I won’t bore you with the details of implementing all of the interfaces), I had a working EntityCollectionView<T> class, and I started to crank out the EntityCollectionViewConverter and immediately ran into a roadblock.  Because my class is generic, but I don’t know my entity’s type at design time, I can’t construct the view instance very easily.  Instinct was to make the view class non-generic, but that doesn’t work because there isn’t a non-generic type for EntityCollection, so my view type must be generic in order to work with EntityCollection<T>.

Building up the generic instance wasn’t very difficult though, using a couple of good friends: GetGenericArguments() and MakeGenericType().

   1: /// <summary>
   2: /// Factory for creating an <see cref="EntityCollectionView{T}"/> instance from an
   3: /// <see cref="EntityList{T}"/>.
   4: /// </summary>
   5: /// <param name="entityList">The <see cref="EntityList{T}"/> to create a view from.</param>
   6: /// <returns>
   7: /// An <see cref="EntityCollectionView{T}"/> that wraps around the <paramref name="entityList"/>.
   8: /// </returns>
   9: public static IEditableCollectionView FromEntityList(EntityList entityList)
  10: {
  11:     Type listType = entityList.GetType();
  12:     Type[] genericArgs = listType.GetGenericArguments();
  14:     if (genericArgs.Length != 1)
  15:     {
  16:         throw new ArgumentException("entityList must be an EntityList<T>");
  17:     }
  19:     Type collectionViewType = typeof(EntityCollectionView<>).MakeGenericType(genericArgs.Single());
  20:     return (IEditableCollectionView)Activator.CreateInstance(collectionViewType, entityList);
  21: }
  23: /// <summary>
  24: /// Factory for creating an <see cref="EntityCollectionView{T}"/> instance from an
  25: /// <see cref="EntityList{T}"/>.
  26: /// </summary>
  27: /// <param name="entityCollection">The <see cref="EntityCollection{T}"/> to create a view from.</param>
  28: /// <returns>
  29: /// An <see cref="EntityCollectionView{T}"/> that wraps around the <paramref name="entityCollection"/>.
  30: /// </returns>
  31: public static IEditableCollectionView FromEntityCollection(object entityCollection)
  32: {
  33:     Type collectionType = entityCollection.GetType();
  34:     Type genericType = collectionType.GetGenericTypeDefinition();
  36:     if (!typeof(EntityCollection<>).IsAssignableFrom(genericType))
  37:     {
  38:         throw new ArgumentException("entityCollection must be an EntityCollection<T>");
  39:     }
  41:     Type collectionViewType = typeof(EntityCollectionView<>).MakeGenericType(collectionType.GetGenericArguments().Single());
  42:     return (IEditableCollectionView)Activator.CreateInstance(collectionViewType, entityCollection);
  43: }

We can even add a single method that makes the decision between which factory method to call.

   1: /// <summary>
   2: /// Factory for creating an <see cref="EntityCollectionView{T}"/> instance from either
   3: /// an <see cref="EntityList{T}"/> or an <see cref="EntityCollection{T}"/>.
   4: /// </summary>
   5: /// <param name="entityListOrEntityCollection">
   6: /// Either an <see cref="EntityList{T}"/> or an <see cref="EntityCollection{T}"/>.
   7: /// </param>
   8: /// <returns>
   9: /// An <see cref="EntityCollectionView{T}"/> that wraps around the <paramref name="entityListOrEntityCollection"/>.
  10: /// </returns>
  11: public static IEditableCollectionView From(object entityListOrEntityCollection)
  12: {
  13:     EntityList list = entityListOrEntityCollection as EntityList;
  15:     if (list != null)
  16:     {
  17:         return FromEntityList(list);
  18:     }
  19:     else
  20:     {
  21:         return FromEntityCollection(entityListOrEntityCollection);
  22:     }
  23: }

These three methods have to exist in a non-generic type, so I created a static class for EntityCollectionView to house them.  Now, we can write a very simple converter that will produce the EntityCollectionView<T> from an EntityList<T> or an EntityCollection<T>.

   1: using System;
   2: using System.Windows.Data;
   4: namespace QuickSilverlight.RiaServices
   5: {
   6:     /// <summary>
   7:     /// Convert an <see cref="EntityList{T}"/> or an <see cref="EntityCollection{T}"/> into
   8:     /// an <see cref="EntityCollectionView{T}"/> that can be used for DataForm binding.
   9:     /// </summary>
  10:     public class EntityCollectionViewConverter : IValueConverter
  11:     {
  12:         public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  13:         {
  14:             // Can't construct an EntityCollectionView<T> because we don't easily know the <T>.
  15:             // Let this factory method do the work for us.
  16:             return EntityCollectionView.From(value);
  17:         }
  19:         public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  20:         {
  21:             throw new NotSupportedException("The EntityCollectionViewConverter does not support two-way binding");
  22:         }
  23:     }
  24: }

Using the binding converter is a breeze:

    <qs:EntityCollectionViewConverter x:Key="EntityCollectionViewConverter" />
<df:DataForm x:Name="OrderForm"
             ItemsSource="{Binding Path=CustomerContext.Customers, Converter={StaticResource EntityCollectionViewConverter}}" />

Binding to an EntityList directly off of the DomainContext works great.  You get the Add/Remove buttons on the DataForm, and all edit operations and navigation operations work just fine.

I did notice that I’m not entirely handling the EntityCollection Master/Details scenarios, because when you Add or Remove, it only adds to and removes from the details EntityCollection and not the DomainContext itself.  Without having access to the DomainContext, I don’t think I can make it do that.  I’d love to hear thoughts on that scenario to see if it’s something I should work into this solution.

Source code and binaries available here:

I hope this helps, but any and all feedback is welcomed.