Improved binding support in EntitySet and EntityCollection


For our WCF RIA Services V1.0 SP1 Beta release we were finally able to improve binding support for EntitySets and EntityCollections. In our V1 release, EntitySet and EntityCollection did not support adding and removing entities through the IEditableCollectionView interface when used as the ItemsSource in a DataGrid or DataForm. While the details of the problem will only be familiar to some, if you’ve had difficulty adding or removing in a master/details scenario, this is the likely cause.

A Master/Details Example

In this section, I’ll put together a short sample showing how this feature enables a simple master/details scenario. I want to focus mainly on the client side of things so we’ll keep the DomainService simple.

  [EnableClientAccess]
  public class SampleDomainService : DomainService
  {
    public IEnumerable<SampleEntity> GetEntities() {…}

    public void CreateEntity(SampleEntity entity) {…}

    public void UpdateEntity(SampleEntity entity) {…}

    public void DeleteEntity(SampleEntity entity) {…}
  }

  public class SampleEntity
  {
    [Key]
    public int Id { get; set; }

    public string Name { get; set; }
  }

When the RIA Services codegen runs, it will generate the SampleDomainContext and SampleEntity types on the client. I’ve omitted most of the generated code, but here are the two members we’ll use later in the sample.

  public class SampleDomainContext : DomainContext
  {
    public EntitySet<SampleEntity> SampleEntities
    {
      get {…}
    }
    
    public EntityQuery<SampleEntity> GetEntitiesQuery() {…}
  }

My approach on the client side of the application is simple. I use a DataGrid to show a list of the SampleEntities and the DataForm to add, remove, and edit. I’ve defined my SampleDomainContext in the xaml to make the binding simple and explicit.

  <UserControl >

    <UserControl.Resources>
      <web:SampleDomainContext x:Key="myDomainContext"/>
    </UserControl.Resources>

    <StackPanel x:Name="LayoutRoot" Background="White">
      <StackPanel Orientation="Horizontal" Margin="2">

<
sdk:DataGrid Name="myDataGrid" ItemsSource="{Binding SampleEntities,
Source={StaticResource myDomainContext}}" IsReadOnly="True" /> <toolkit:DataForm Name="myDataForm" Width="200" ItemsSource="{Binding SampleEntities,
Source={StaticResource myDomainContext}}" CurrentItem="{Binding SelectedItem,
ElementName=myDataGrid, Mode=TwoWay}" CommandButtonsVisibility="Add,Delete,Commit,Cancel" />
</
StackPanel>
<
Button Name="Submit" Content="Submit Changes" Click="Submit_Click"
HorizontalAlignment="Left" Margin="2"/>
</
StackPanel> </UserControl>

The code behind is just as simple. I pull the SampleDomainContext from the resources (that I declared in xaml) and use it to load and save the SampleEntities. The only complexity here is tracking the DataForm’s Editing state to make sure the edit is committed and the validation errors are addressed before submitting changes.

  public partial class MainPage : UserControl
  {
    private readonly SampleDomainContext _context;
    private bool _isDataFormEditing;

    public MainPage()
    {
      InitializeComponent();

      this._context =
(SampleDomainContext)this.Resources["myDomainContext"]; this._context.Load(this._context.GetEntitiesQuery()); // This is an unfortunate workaround to track the
// DataForm Editing state
this.myDataForm.AddingNewItem +=
(sender, e) => this._isDataFormEditing = true; this.myDataForm.BeginningEdit +=
(sender, e) => this._isDataFormEditing = true; this.myDataForm.EditEnded +=
(sender, e) => this._isDataFormEditing = false; } private void Submit_Click(object sender, RoutedEventArgs e) { if (!this._isDataFormEditing || this.myDataForm.CommitEdit()) { this._context.SubmitChanges(); } } }

In the end, I have a nice little Master/Details application.

image

To Sum it Up

This sample is pretty simple. The point I really want to emphasize here is that you can now bind EntitySets and EntityCollections to Silverlight controls like the DataGrid and DataForm and have them work like you would expect them to. On top of that, these types will show improved compatibility in features like drag-and-drop and as well as in third party controls.

Comments (13)

  1. Befuddled says:

    So which/where/what is the EntitySet or EntityCollection? Those types appear in the title of your blog but I do NOT see them anywhere in the code samples you present. Not very helpful for befuddled beginners who are not WCF RIA Services Gurus. How many more iterations before we get clear coherent comprehensive practical and useable docs and demos for those of us who are no where near as smart as the Gods presenting at PDC10?

  2. kylemc says:

    Good point. I'll update the post to show the generated code. (but until I do, SampleDomainContext.SampleEntities is an EntitySet<SampleEntity>)

  3. cadessi says:

    If I expose the EntitySet directly in my viewmodel, how do I keep it in sync with the DomainContext?

    // MyViewModel

    public EntitySet<User> Users

    {

       get { return this.Context.Users; }

    }

    private void InsertUser()

    {

       User user = new User() { Username = "user" };

       this.Context.Users.Add(user);

       this.Context.SubmitChanges(

           operation =>

           {

               if (operation.HasError)

               {

                   operation.MarkErrorAsHandled();

                   // —> Is this really necessary?

                   this.Context.Users.Remove(user);

               }    

                   // Issue another request to reload the entityset

                   LoadData();        

           }

           , null);

    }

    Also, should I manually issue another request to reload the entityset everytime I submit changes (just in case somebody else did modify the collection) or is there a way to do automatically? I've seen people overriding the Load method in a partial class for the client-side DomainContext:

    public override LoadOperation Load(EntityQuery query,

    LoadBehavior loadBehavior, Action<LoadOperation> callback, object userState)

    {

      return base.Load(query, LoadBehavior.RefreshCurrent, callback, userState);

    }

    Thanks.

  4. kylemc says:

    Your EntitySet is always 'in sync' with your DomainContext, so I'm not positive quite what your question is. I'll try to answer based on your code, though.

    First, it shouldn't be necessary to remove a user on an error (there's the possibility it's a validation error or something else you can address and resubmit). In some scenarios it might be a good idea, but I'm not sure there's a general rule. In this one, calling Context.RejectChanges() might also be an option to consider.

    Second, if you want to pick up new entity additions or edits to entities you did not modify, you'll need to reload the data. In this case, it won't matter what LoadBehavior you use because all the entities will be in an 'unmodified' state. It sounds to me like you do want to reload the data for your scenario, but again there isn't a general rule for this.

  5. cadessi says:

    Sorry, the question was that given the above scenario, binding directly to the EntitySet leads to a very awful user experience (IMHO). I mean, as soon as the newly created entity is added to the context:

    this.Context.Users.Add(user);

    it'll be shown in the DataGrid. Thereafter, when the Context.SubmitChanges() call fails, the entity is removed from the Context, either by reloading or calling Context.RejectChanges(). I think what the user would expect is the entity not to be shown in the DataGrid unless the Context.SumbmitChanges() success. Now, i can achieve exactly this by wrapping the EntitySet in a PagedCollectionView, but not binding directly to the EntitySet.

  6. kylemc says:

    I see. Yes, binding to an EntitySet gives you the binding experience you're describing. It's good for some scenarios, but maybe not the best when you're adding an entity (say from a details windows). In this case, it's fine to continue to use a view if that's the best behavior. I know the default binding support won't be exactly what people want for every scenario. I'm still working on a CollectionView implementation that will provide all this flexibility with RIA, but more on that later… 😉

  7. Benito says:

    Kylemc,

    Do you have the code above in VB? I have a datagrid and a dataform and I want to be able to add information to the table from the dataform. Right now Im able edit the information on the table, but the add button is grey out.

  8. JackyW says:

    Hi,

    if you don't want make the grid sync with datacontext, add ToList() into Users property.

    public EntitySet<User> Users

    {

      get { return this.Context.Users.ToList(); }

    }

  9. JackyW says:

    Hi,

    if you don't want make the grid sync with datacontext, add ToList() into Users property.

    public EntitySet<User> Users

    {

      get { return this.Context.Users.ToList(); }

    }

    i thinkd

  10. Aditya says:

    I followed your example here. Everything works fine except the cancel button when I am adding a new item via data form. Can you upload your project so I can compare and see if I missed anything.

    Thanks.

  11. Aditya says:

    I should have been a little precise in my previous comment. When I add a new item using Data Form and I click OK, I get validation error message. I decide to stop and I click on Cancel only to get "Object reference not set to an instance of an object." error message. What am I doing wrong and how do I fix it?

    Thanks.

  12. shanec112 says:

    Aditya,

    Had the same problem vb.net when using a property on the view model to expose a ria enity as the current selected item.

    Changed the currentitem binding to be the currentitem on the Icollection view and it works.

    Works

    CurrentItem="{Binding Collection.CurrentItem,Mode=OneWay}"  ItemsSource="{Binding Collection,Mode=TwoWay}"

    Does Not Work

    CurrentItem="{Binding CurrentItem,Mode=OneWay}"  ItemsSource="{Binding Collection,Mode=TwoWay}"

       Public Property CurrentItem As Data.Server.C_CATALG

           Get

               Return _CurrentItem

           End Get

           Set(ByVal value As Data.Server.C_CATALG)

               If Not Object.Equals(_CurrentItem, value) Then

                   _CurrentItem = value

                   NotifyPropertyChanged("CurrentItem")

               End If

           End Set

       End Property

    Or am I doing something else wrong too !

  13. Bob says:

    How about quit playing around with all this useless crap like Silverlight, WCF(whatever that is), RIA (whatever that is) and build sorting and proper binding into the Entity Framework. And, FOR GOD'S SAKE, STOP STOP STOP posting C# code. WE NEED THE STINKING EXAMPLES IN VISUAL BASIC.