Accessing Master-Detail Data through ADO.NET Data Service in a Silverlight Application (Part 2)

In Part 1 of this post, we developed our Silverlight application to access data through an ADO.NET Data Service. So far we are able to load the data at run time and navigate through the data records back and forth. Let’s now develop more functionality to update data, delete records, and add new records.

Updating Data Records

Our data is accessed through the Service Reference we added to our Silverlight application. However, the Service Reference does not keep track of the changes that we make in the application. This is actually due to a limitation of ADO.NET Data Service Client Library. We have to have a mechanism to notify the DataContext object about changes. In Part 1, we added the data we retrieved through the service into an ObservableCollection<T> so that we can get notifications when data get added, updated or removed. We can then notify the DataContext by implementing System.ComponentModel.INotifyPropertyChanged on the DataContract classes.

For example, in order to notify NorthwindEntities (the DataContext object) about changes of the CompanyName property of Customer (the DataContract object), we can do the following:

public partial class Customer : INotifyPropertyChanged

{

public event PropertyChangedEventHandler PropertyChanged;

partial void OnCompanyNameChanged()

{

if (PropertyChanged != null)

{

PropertyChanged(this, new PropertyChangedEventArgs("CompanyName"));

}

}

}

The OnCompanyNameChanged() partial method is defined (yet not implemented) in the generated Reference.cs file of the Service Reference. You can find this file in Solution Explorer under “Service References” – “NorthwindServiceReference” – “Reference.datasvcmap” – “Reference.cs”.

For this application, we are creating a class file “NorthwindEntitiesChangeManager.cs” in the Silverlight application. For every updatable property of Customer, Order, and Order_Details, we implement the On<PROPERTY>Changed partial method in the same manner as above.

We’ll also need to notify the DataContext object about DataContract object changes. Therefore, we are modifying the OnDataLoadComplete method to hook up the change events:

private void OnDataLoadComplete(IAsyncResult result)

{

List<Customer> custs = qry.EndExecute(result).ToList();

foreach (Customer cust in custs)

{

cust.PropertyChanged += new PropertyChangedEventHandler(cust_PropertyChanged);

foreach (Order ord in cust.Orders)

{

ord.PropertyChanged += new PropertyChangedEventHandler(ord_PropertyChanged);

foreach (Order_Details od in ord.Order_Details)

{

od.PropertyChanged += new PropertyChangedEventHandler(od_PropertyChanged);

}

}

data.Add(cust);

}

cvs.Source = data;

}

Handling the PropertyChanged events is to call UpdateObject() method on the DataContext object:

void cust_PropertyChanged(object sender, PropertyChangedEventArgs e)

{

this.ctx.UpdateObject(sender as Customer);

}

void ord_PropertyChanged(object sender, PropertyChangedEventArgs e)

{

this.ctx.UpdateObject(sender as Order);

}

void od_PropertyChanged(object sender, PropertyChangedEventArgs e)

{

this.ctx.UpdateObject(sender as Order_Details);

}

Deleting Data Records

To delete records, we need to call the DeleteObject() method on the DataContext object.

The following code deletes the current Customer, and all the related Orders and Order_Details, similar to a CASCADE deletion on the database backend:

void DeleteItem_Click(object sender, RoutedEventArgs e)

{

string msg = "Are you sure you want to delete the current customer and all the related order information?";

if (HtmlPage.Window.Confirm(msg))

{

Customer cust = cvs.View.CurrentItem as Customer;

foreach (Order ord in cust.Orders)

{

foreach (Order_Details od in ord.Order_Details)

{

this.ctx.DeleteObject(od);

}

this.ctx.DeleteObject(ord);

}

this.ctx.DeleteObject(cust);

this.data.Remove(cust);

this.bindingNavigator1.RecordCount--;

this.bindingNavigator1.RefreshControls();

}

}

Similarly, we can delete the current selected Order and its Order_Details using the following code:

private void btnDeleteOrder_Click(object sender, RoutedEventArgs e)

{

string msg = "Are you sure you want to delete the current order and all the order details?";

if (HtmlPage.Window.Confirm(msg))

{

Order ord = this.ordersDataGrid.SelectedItem as Order;

int curOrderID = ord.OrderID;

foreach (Customer cust in this.data)

{

if (cust.CustomerID == ord.CustomerID)

{

foreach (Order o in cust.Orders)

{

if (o.OrderID == curOrderID)

{

List<Order_Details> order_DetailsToRemove = new List<Order_Details>();

foreach (Order_Details od in o.Order_Details)

{

ctx.DeleteObject(od);

order_DetailsToRemove.Add(od);

}

foreach (Order_Details odToRemove in order_DetailsToRemove)

{

o.Order_Details.Remove(odToRemove);

}

ctx.DeleteObject(o);

cust.Orders.Remove(o);

break;

}

}

break;

}

}

}

}

And here’s the code to delete the currently selected Order_Details:

private void btnDeleteOrder_Detail_Click(object sender, RoutedEventArgs e)

{

string msg = "Are you sure you want to delete the current order detail?";

if (HtmlPage.Window.Confirm(msg))

{

Order_Details curOrder_Detail = this.order_DetailsDataGrid.SelectedItem as Order_Details;

ctx.DeleteObject(curOrder_Detail);

Order curOrder = this.ordersDataGrid.SelectedItem as Order;

int curOrderID = curOrder.OrderID;

foreach (Customer cust in this.data)

{

if (cust.CustomerID == curOrder.CustomerID)

{

foreach (Order o in cust.Orders)

{

if (o.OrderID == curOrderID)

{

o.Order_Details.Remove(curOrder_Detail);

break;

}

}

break;

}

}

}

}

Adding New Data Records

To add new records, we can call the Create<OBJECT> method on the DataContract object, and then call the AddTo<OBJECTS> method of the DataContext object. For example, to add a new Customer, we can use the following:

Customer cust = Customer.CreateCustomer("ABCDE", "ABCDE Corp");

this.ctx.AddToCustomers(cust);

By drag-dropping data sources from the Data Sources window, we can easily create new data entry UI as Silverlight controls. This is the new data entry UI I created by stripping data binding XAML from the generated UI:

NewCustomerDataEntry

The Orders table in the database has OrderID as an IDENTITY field (auto-increment), so we can create a new Order object by setting -1 as OrderID:

Order ord = Order.CreateOrder(-1);

Customer cust = this.cvs.View.CurrentItem as Customer;

ord.CustomerID = cust.CustomerID;

// Set other properties …

cust.Orders.Add(ord);

this.ctx.AddToOrders(ord);

Here’s the code to create a new Order_Details:

Order ord = this.ordersDataGrid.SelectedItem as Order;

int productID = Int32.Parse(this.order_DetailsDataEntry.productIDTextBox.Text); //32

decimal unitPrice = Decimal.Parse(this.order_DetailsDataEntry.unitPriceTextBox.Text);

short quantity = short.Parse(this.order_DetailsDataEntry.quantityTextBox.Text); //5

float discount = float.Parse(this.order_DetailsDataEntry.discountTextBox.Text); //0.25f

Order_Details od = Order_Details.CreateOrder_Details(ord.OrderID, productID, unitPrice, quantity, discount);

ord.Order_Details.Add(od);

this.ctx.AddToOrder_Details(od);

Saving Changes

To save changes, we can simply call the SaveChanges() method on the DataContext object. The code is similar to the following (note you may want add code to handle data update exceptions not done here):

void SaveItem_Click(object sender, RoutedEventArgs e)

{

ctx.BeginSaveChanges(System.Data.Services.Client.SaveChangesOptions.Batch, new AsyncCallback(OnSaveChangesComplete), null);

}

void OnSaveChangesComplete(IAsyncResult result)

{

ctx.EndSaveChanges(result);

}

Summary

Up to this point, we have developed a Silverlight application capable of loading data through an ADO.NET Data Service, navigating through the data records, performing updating/deleting/inserting operations, and finally committing changes to the database backend. Data validation and error handling during save is not handled here but users can easily hook up their own business logic.

Hope you will enjoy developing data applications with Silverlight.