Mike Taulty's Blog
Bits and Bytes from Microsoft UK
Silverlight and ADO.NET Data Services ( 2 )
Mike Taulty's Blog

Mike's Badges

Follow on Twitter
View mike's profile on slideshare
Add to Technorati Favorites
CW Blog Awards

Following on from this last post, if I start to edit the data in the grid and change some value then the property value will change in the underlying class but nothing else will happen. That is, the NorthwindEntities class which is managing my data for me client-side ( which is derived from DataServiceContext ) isn't going to be aware of those modifications.

Why not? If we have a look at a property on a generated class such as this one for the CompanyName on the Customers class;

        public string CompanyName
        {
            get
            {
                return this._CompanyName;
            }
            set
            {
                this.OnCompanyNameChanging(value);
                this._CompanyName = value;
                this.OnCompanyNameChanged();
            }
        }

Now, OnCompanyNameChanging and OnCompanyNameChanged are partial methods with no implementation by default so nothing's going to happen when something like the DataGrid changes a value such as CompanyName.

Similarly, the generated entity classes such as Customers do not implement INotifyPropertyChanged which means that changes in the data will not be reflected in the UI anywhere as the UI doesn't have the necessary events to sync up to. This would also be true of an ADO.NET Data Services client generated for the full .NET framework ( such as WPF ) right now as it's the same tool that's in use.

( It wouldn't be true if you were using generated code from the Entity Framework directly in your UI ( i.e. a 2-tier solution ) because the Entity Framework ObjectContext and generated entity classes do set up a relationship where one "tracks" the other via the ObjectContext's ObjectStateManager and the IEntityWithChangeTracker interface and EntityObject's implementation of it ).

So, we're not going to get automatic, 2-way databinding on these entity classes just yet ( I'm hoping that RTM might do that but I've no inside knowledge on that ) and there's not really any way that you want to try and manually add it if you've got a lot of generated classes with a lot of generated properties - way too much manual work there and ( AFAIK ) datasvcutil.exe isn't extensible.

So you might want to look at something like Josh's script over here which does a crafty search-and-replace for you.

Regardless, if I edit data in my DataGrid as I've got it currently then the edits will make it through to the underlying property on the data bound object. So, if I change my UI to add a "Save" button;

<UserControl
  x:Class="BlogPostSL.Page"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data">
  <Grid x:Name="LayoutRoot" Background="White">
    <Grid.RowDefinitions>
      <RowDefinition
        Height="8*" />
      <RowDefinition />
      <RowDefinition />
      <RowDefinition />
    </Grid.RowDefinitions>
    <data:DataGrid
      x:Name="dataGrid"
      Margin="10"
      AutoGenerateColumns="True"
      ItemsSource="{Binding}" />
    <Button
      Margin="10"
      Content="Get Data"
      Grid.Row="1"
      Click="OnGetData" />
    <Button
      Margin="10"
      Content="Save Data"
      Grid.Row="2"
      Click="OnSaveData" />
  </Grid>
</UserControl>

and then add a little code to my code-behind class ( I've not included the whole class here, just the new method );

 void OnSaveData(object sender, EventArgs args)
    {
      proxy.BeginSaveChanges((asyncResult) =>
        {
          try
          {
            proxy.EndSaveChanges(asyncResult);
          }
          catch (Exception ex)
          {
            // TODO
            Debugger.Break();
          }
        }, null);
    }

Then nothing happens when that code inside my OnSaveData function runs. If I peer inside the proxy instance a little bit with the debugger then I can see a bunch of my Customers entities on a list of EntityDescriptor with a status of Unchanged. So, we need to make sure that our grid is setting the right instance to be updated when it has been updated as in;

    public Page()
    {
      InitializeComponent();

      this.Loaded += OnLoaded;

      dataGrid.AutoGeneratingColumn += OnGeneratedColumn;
      dataGrid.CommittingEdit += OnCommittingEdit;
    }
    void OnCommittingEdit(object sender, DataGridEndingEditEventArgs e)
    {
      proxy.UpdateObject(e.Row.DataContext);
    }

Then, when I update data and click my Save button then I get my data saved back into the database. Naturally, this is being done (AFAIK!) in line with the concurrency options and if I wanted to I could look to do batch updates rather than individual PUTs to make it happen as in;

    void OnSaveData(object sender, EventArgs args)
    {
      proxy.BeginSaveChanges(SaveChangesOptions.Batch, (asyncResult) =>
        {
          try
          {
            proxy.EndSaveChanges(asyncResult);
          }
          catch (Exception ex)
          {
            // TODO
            Debugger.Break();
          }
        }, null);
    }

I can leave that in place and handle deletes by, for instance, trapping the delete keypress on a grid row and then making a call to DeleteObject. I might do that by the following ( again, only listed the added code not the whole code for my Page class );

  public partial class Page : UserControl
  {
    private bool inEdit;

    public Page()
    {
      InitializeComponent();

      this.Loaded += OnLoaded;

      dataGrid.AutoGeneratingColumn += OnGeneratedColumn;
      dataGrid.BeginningEdit += OnBeginningEdit;
      dataGrid.CommittingEdit += OnCommittingEdit;
      dataGrid.CancelingEdit += OnCancellingEdit;
      dataGrid.KeyDown += OnGridKeyDown;
    }
    void OnCancellingEdit(object sender, DataGridEndingEditEventArgs e)
    {
      inEdit = false;
    }
    void OnBeginningEdit(object sender, DataGridBeginningEditEventArgs e)
    {
      inEdit = true;
    }
    void OnCommittingEdit(object sender, DataGridEndingEditEventArgs e)
    {
      proxy.UpdateObject(e.Row.DataContext);
    }
    void OnGridKeyDown(object sender, KeyEventArgs e)
    {
      if ((e.Key == Key.Delete) && (!inEdit))
      {
        // Single select for me.
        if (dataGrid.SelectedItem != null)
        {
          // Tell the DataServiceContext
          proxy.DeleteObject(dataGrid.SelectedItem);

          // Remove from the bound collection, disappears from
          // grid.
          BoundData.Remove(dataGrid.SelectedItem as Customers);
        }
      }
    }
    ObservableCollection<Customers> BoundData
    {
      get
      {
        return (dataGrid.DataContext as ObservableCollection<Customers>);
      }
    }

So, I've a form of update and a form of delete working. Insert can maybe be handlded by just pressing the Insert key, changing that handler to;

    void OnGridKeyDown(object sender, KeyEventArgs e)
    {
      if (!inEdit)
      {
        if (e.Key == Key.Delete)
        {
          // Single select for me.
          if (dataGrid.SelectedItem != null)
          {
            // Tell the DataServiceContext
            proxy.DeleteObject(dataGrid.SelectedItem);

            // Remove from the bound collection, disappears from
            // grid.
            BoundData.Remove(dataGrid.SelectedItem as Customers);
          }
        }
        else if (e.Key == Key.Insert)
        {
          Customers c = new Customers() { Country = "UK" };
          int index = BoundData.IndexOf(dataGrid.SelectedItem as Customers);
          BoundData.Insert(index, c);
          proxy.AddObject("Customers", c);
        }
      }
    }

From there I think the next steps would be to think about inserting related entities ( 1 to many and many-to-many ), checking status codes, handling errors and so on.


Posted Mon, Jun 30 2008 3:38 PM by mtaulty

Comments

どっとねっとふぁんBlog wrote SilverlightとADO.NET Data Servicesを連携させるサンプル
on Mon, Jun 30 2008 7:47 PM
Silverlight and ADO.NET Data Services    Silverlight and ADO.NET Data Services (2)  SilverlightとADO.NET...
Silverlight news for July 1, 2008 wrote Silverlight news for July 1, 2008
on Tue, Jul 1 2008 2:01 AM
null exception null wrote null exception null
on Sun, Jul 13 2008 1:15 PM
(C) Mike Taulty, 2009. All rights reserved. The information in this weblog is provided "AS IS" with no warranties, and confers no rights. This weblog does not represent the thoughts, intentions, plans or strategies of my employer. It is solely my opinion. Inappropriate comments will be deleted at the authors discretion. All code samples are provided "AS IS" without warranty of any kind, either express or implied, including but not limited to the implied warranties of merchantability and/or fitness for a particular purpose.
Powered by Community Server (Non-Commercial Edition), by Telligent Systems