Silverlight - Data - Guide
Silverlight - Data - Guide
Internet: info@[Link]
Web site: [Link]
Sales
E-mail: sales@[Link]
Telephone: 1.800.858.2739 or 1.412.681.4343 (Pittsburgh, PA USA Office)
Trademarks
The ComponentOne product name is a trademark and ComponentOne is a registered trademark of ComponentOne LLC. All
other trademarks used herein are the properties of their respective owners.
Warranty
ComponentOne warrants that the original CD (or diskettes) are free from defects in material and workmanship, assuming
normal use, for a period of 90 days from the date of purchase. If a defect occurs during this time, you may return the defective
CD (or disk) to ComponentOne, along with a dated proof of purchase, and ComponentOne will replace it at no charge. After
90 days, you can obtain a replacement for a defective CD (or disk) by sending it and a check for $25 (to cover postage and
handling) to ComponentOne.
Except for the express warranty of the original CD (or disks) set forth here, ComponentOne makes no other warranties, express
or implied. Every attempt has been made to ensure that the information contained in this manual is correct as of the time it was
written. We are not responsible for any errors or omissions. ComponentOne’s liability is limited to the amount you paid for the
product. ComponentOne is not liable for any special, consequential, or other damages for any reason.
iii
Data for Silverlight Overview
ComponentOne Data™ for Silverlight is an implementation of the standard DataSet, DataTable, and DataView
classes familiar to Windows Forms and [Link] developers. The following topics describe how you can use the
[Link] assembly to implement data-centric Silverlight applications.
Introduction to [Link]
The classes in the [Link] assembly are a subset of those in the [Link] namespace. This means
that you can use your existing [Link]-based code in Silverlight applications. For example, the code below
shows how you can create and populate a DataTable object using [Link]:
Create DataTable
DataTable dt = new DataTable();
1
Note: You must import the [Link] (contains the C1Data classes) and
[Link] (contains Microsoft's DataGrid control) namespaces for the above code to
work.
The DefaultView property returns a DataView object associated with the table. Again, this is exactly the same
mechanism used in [Link]. The DataView object implements an IEnumerable interface that allows it to be
used as a data source for bindable controls and for LINQ queries.
Because the classes in the [Link] assembly are a subset of those in the [Link] namespace, we
will not describe them in detail here. If you are not familiar with these classes, please check the descriptions of the
[Link] and [Link] classes on MSDN.
Data-centric Architecture
Silverlight can be used to build line-of-business and other data-centric applications. This type of application
typically involves the following steps:
1. Get the data from the server.
2. Display and edit the data on the client.
3. Submit the changes back to the server.
Steps 1 and 3 typically rely on Web services to transfer the data and traditional data access strategies to query and
update a database. Step 2 typically involves Silverlight data-bound controls.
Microsoft offers many tools that can be used to perform the server-side part of the job. The latest such tool is
[Link] Data Services, which provides Web-accessible endpoints for data models and integrates with
Silverlight through the [Link] Data Services for Silverlight library ([Link]). A lot has
been written lately about this new technology (see for example "Data Services" in MSDN vol. 23, no. 10,
September 2008).
[Link] Data Services is a powerful new technology that is likely to become a standard for many types of data-
centric applications. However, it's not the only option. The traditional data [Link] classes (DataSet,
DataTable, DataAdapters, and so on) can also be used to retrieve and update data on the server. These classes
have been used by developers since .NET 1.0. They are solid, powerful, and easy to use. Furthermore, many
developers already have a considerable investment in the form of code that has been used and tested for a long
time.
ComponentOne Data for Silverlight is a set of classes that can be used by Silverlight clients to exchange data with
servers using traditional [Link]. The typical steps are as follows:
1. Get the data from the server.
a. Server populates a DataSet object the traditional way (typically using DataAdapter objects to retrieve
the data from a SqlServer database).
b. Server calls [Link] to serialize the DataSet into a stream and sends the stream to the
client.
c. Client uses the [Link] method to de-serialize the stream.
2. Display and edit the data on the client.
a. Client binds the tables in the DataSet to controls (possibly using LINQ queries).
b. User interacts with the controls to view, edit, add, and delete data items.
3. Submit the changes back to the server.
a. Client calls [Link] and [Link] to serialize the changes into a stream and
sends the stream to the server.
b. Server calls [Link] to de-serialize the changes, then uses DataAdapter objects to persist
the changes into the database.
2
The role of C1Data in this scenario is twofold. First, it provides a symmetric serialization mechanism that makes it
easy to transfer data between DataSet objects on the client and on the server. Second, it provides a familiar object
model to manipulate the data on the client.
Note: C1Data does not compete against [Link] Data Services. It allows you to leverage your knowledge
of [Link] and assets you may already have. You can transfer all that to Silverlight with minimal effort, and
migrate to [Link] Data Services gradually, if such a migration is desired.
C1Data is not a legacy technology. You can use it with LINQ, for example. In fact, C1Data enables LINQ
features that are available on desktop but not in Silverlight applications (partial anonymous classes).
The next sections describe the implementation of a simple application that performs the steps described above.
Despite its simplicity, the application shows how to perform the four CRUD operations (Create, Read, Update,
Delete) that are required of most data-centric applications.
3
Create the UI
The user interface will consist of two data grids (Categories and Products) and a few buttons used to add and delete
items and to commit the changes.
To create the user interface, open the [Link] file and in Source view copy the following XAML onto the
page:
<UserControl x:Class="[Link]"
xmlns="[Link]
xmlns:x="[Link]
xmlns:swc=
"clr-namespace:[Link];assembly=[Link]"
>
<Grid x:Name="LayoutRoot" Background="White" >
4
<swc:DataGrid x:Name="_gridProducts" Margin="5" [Link]="4" />
5
The image below shows the Solution Explorer window after this step is completed:
In addition to the database file, we need a mechanism to transfer data from and to the database. In this sample, we
will accomplish this using a utility class called SmartDataSet. SmartDataSet extends the regular [Link]
DataSet class and adds the following:
A ConnectionString property that specifies the database type and location.
A Load method that loads data from selected tables.
An Update method that saves changes back into the database.
The SmartDataSet is convenient but not necessary. It knows how to create and configure DataAdapter objects
used to load and save data, but you could also write standard [Link] code to accomplish the same thing.
Because it only uses standard [Link] techniques, we will not list it here.
To add the [Link] file to the project, complete the following:
1. Right-click the MasterDetailWeb node in the Solution Explorer, select Add | Existing Item.
2. In the Add Existing Item dialog box, locate the [Link] file in the [Link] distribution
package and click Add to add it to the project.
6
We could implement the server side as a generic handler class (ashx), as a Web Service (asmx), or as a Silverlight-
enabled WCF service (SVC). For this sample, any of the choices would do. We will choose a classic Web Service.
Follow these steps to create the service:
1. Right-click the MasterDetailWeb project.
2. Select Add | New Item.
3. In the Add New Item dialog box, select Web from the list of categories in the left pane.
4. In the right pane, select the Web Service template from the list of templates.
5. Name the new service "[Link]".
The dialog box should look like the image below.
7
// Create DataSet with connection string
var ds = GetDataSet();
// Persist to stream
var ms = new [Link]();
[Link](ms, [Link]);
8
foreach (DataTable dt in [Link])
{
foreach (DataRow dr in [Link])
{
switch (state)
{
case [Link]:
[Link]();
break;
case [Link]:
[Link]();
break;
case [Link]:
[Link]();
break;
}
}
}
// Make sure file is not read-only (source control often does this...)
FileAttributes att = [Link](mdb);
if ((att & [Link]) != 0)
{
att &= ~[Link];
[Link](mdb, att);
}
9
The method starts by locating the database file, making sure it exists, and checking that it is not read-only
(or the updates would fail). Once that is done, it creates a new SmartDataSet, initializes its
ConnectionString property, and returns the newly created SmartDataSet to the caller.
10
using [Link];
using [Link];
using [Link];
11
{
Uri uri = [Link];
string uriString = [Link];
int ls = [Link]('/');
return new Uri([Link](0, ls + 1) + relativeUri);
}
The GetDataService method instantiates and returns a new DataServiceSoapClient object. We don't use
the default constructor because that would refer to the development environment ([Link] and so
on). It would work correctly on the development machine, but would break when the application is
deployed. Also, the default constructor uses a 65k buffer that might be too small for our data transfers. The
above GetDataService method implementation takes care of both issues.
The LoadData method above instantiates the service and invokes the GetDataAsync method. When the
method finishes executing, it invokes the svc_DataCompleted delegate. The delegate instantiates a
DataSet object, uses the ReadXml method to de-serialize the data provided by the server, then calls
BindData to bind the data to the controls on the page.
Note: This is one of the most important features of the [Link] DataSet class. It uses the
same XML schema that [Link] DataSet objects use. This allows applications to serialize data on the
client and de-serialize it on the server, or vice-versa. The fact that the object models are very similar also
makes things substantially easier for the developer.
Note: If you have ever written WinForms or [Link] applications, this code should look familiar. This is one of
the strengths of [Link]. It allows you to leverage your knowledge of [Link] in Silverlight
applications. You can use the DataSet object model to inspect and modify the tables available for data-binding,
including their schemas and data, as well as the data relations, keys, and so on.
The only slightly unusual statement is the one that creates a DataView object and specifies which columns should
be included in the view. This is an extension provided by the [Link] implementation that is not
present in the original [Link] DataView class.
If you run the project now, you should see the data retrieved from the server:
12
Before we continue with the sample, a few comments are in order.
Although the code that binds the data to the controls looks familiar, what happens under the covers is quite
different from the traditional WinForms and [Link] scenarios. All data binding in WPF and Silverlight is done
through reflection. But the DataRowView objects exposed by DataView collections do not have properties that
match the columns in the underlying DataTable ("CategoryID", "CategoryName", "Description", and so on).
Note: The binding is possible because the DataView class dynamically creates anonymous types that derive from
DataRowView and expose additional properties that correspond to the table columns. Controls can then use
reflection to find these properties and bind to them.
The DataRow class also leverages this mechanism through its GetRowView method. This method builds a
wrapper object that exposes selected properties of the DataRow and can be used for data binding. You can use this
method to in your LINQ queries.
For example, code below builds a LINQ query that selects all categories that start with "C" and exposes their name
and description:
_gridCategories.ItemsSource =
from dr
in [Link]
where ((string)dr["CategoryName"]).StartsWith("C")
select [Link]("CategoryName", "Description");
13
If you call GetRowView with no parameters, all columns are exposed.
14
// Load data, hook up event handlers
public Page()
{
InitializeComponent();
LoadData();
_gridCategories.SelectionChanged += _gridCategories_SelectionChanged;
_btnAdd.Click += _btnAdd_Click;
_btnRemove.Click += _btnRemove_Click;
}
The event handlers are simple, they look like regular [Link] code you would write on a WinForms
application:
// Add a new row
private void _btnAdd_Click(object sender, RoutedEventArgs e)
{
DataTable dt = _ds.Tables["Categories"];
DataRow newRow = [Link]();
newRow["CategoryName"] = "New category";
newRow["Description"] = "This is a new category...";
[Link](newRow);
}
// Delete a row
private void _btnRemove_Click(object sender, RoutedEventArgs e)
{
DataRowView drv = _gridCategories.SelectedItem as DataRowView;
if (drv != null)
{
DataRow dr = [Link]();
[Link]();
}
}
If you run the application now, you will be able to add, remove, and modify the items displayed on the grids.
Note: The DataSet we are using contains not only the tables you see, but also the DataRelation that connects
the two tables. That relation came from the MDB file and was downloaded from the server along with the data.
The relation has a ChildKeyConstraint property that specifies, among other things, that delete operations should
cascade through the tables. In other words, if you delete a category, all products that belong to that category will
also be automatically deleted.
_gridCategories.SelectionChanged += _gridCategories_SelectionChanged;
_btnAdd.Click += _btnAdd_Click;
_btnRemove.Click += _btnRemove_Click;
15
_btnCommit.Click += _btnCommit_Click;
}
And here is the implementation for the event handler:
// Commit changes to server
private void _btnCommit_Click(object sender, RoutedEventArgs e)
{
SaveData();
}
And here is the implementation for the SaveData method, which does the real work:
// Save data back into the database
void SaveData()
{
if (_ds != null)
{
// get changes of each type
byte[] dtAdded = GetChanges([Link]);
byte[] dtModified = GetChanges([Link]);
byte[] dtDeleted = GetChanges([Link]);
// Invoke service
var svc = new GetDataService();
[Link] += svc_UpdateDataCompleted;
[Link](dtAdded, dtModified, dtDeleted);
}
}
void svc_UpdateDataCompleted(object sender, UpdateDataCompletedEventArgs e)
{
if ()
{
throw new Exception("Error updating data on the server: " + [Link]);
}
_tbStatus.Text = "Changes accepted by server.";
_ds.AcceptChanges();
}
The method starts by calling the GetChanges method to build three byte arrays. Each one represents a DataSet
with the rows that have been added, modified, or deleted since the data was downloaded from the server. Then the
method invokes the Web service we implemented earlier and listens for the result. If any errors were detected while
updating the server, we throw an exception (real applications would deal with the error in a more elegant way).
The only piece still missing is the GetChanges method. Here it is:
byte[] GetChanges(DataRowState state)
{
DataSet ds = _ds.GetChanges(state);
if (ds != null)
{
MemoryStream ms = new MemoryStream();
[Link](ms);
return [Link]();
}
return null;
}
The method uses the [Link] method to obtain a new DataSet object containing only the rows that
have the DataRowState specified by the caller. This is the same method available on the [Link] DataSet
class.
16
The method then serializes the DataSet containing the changes into a MemoryStream, and returns the stream
contents as a byte array.
Try running the application now. Make some changes, then click the "Commit Changes" button to send the
changes to the server. If you stop the application and start it again, you should see that the changes were indeed
persisted to the database.
Note: The update may fail depending on the changes you make. For example, if you delete one of the existing
categories, all products that belong to that category will also be removed from the DataSet on the client. When
you try to apply these changes to the server, the transaction will likely fail because the database may contain
tables that still refer to the products you are trying to delete (the Orders table in this case). To test the
delete/commit action, try creating a category, committing the changes, then deleting the new category and
committing again. This will succeed because the new category won't have any products associated with it in the
database.
A real application would have to deal with this type of problem more intelligently. For example, we could have
loaded the Orders table as well, inspect the DataSet to detect whether the user is trying to delete an item that
he should not, and issue a warning. That is left as an exercise for the reader.
// Persist to stream
var ms = new MemoryStream();
using (var sw = new C1.C1Zip.C1ZStreamWriter(ms))
[Link](sw, [Link]);
//[Link](ms, [Link]);
17
Decompress the Data on the Client
To decompress the data on the client, we would modify the MasterDetail project by adding a reference to the
[Link] library (Silverlight version), then change the svc_GetDataCompleted method in the [Link]
file as follows:
void svc_GetDataCompleted(object sender, GetDataCompletedEventArgs e)
{
// Handle errors
if ([Link] != null)
{
_tbStatus.Text = "Error downloading data...";
return;
}
Note: Without compression, the application transfers about 150k bytes of data on startup. After making these
small changes, the initial data transfer is reduced to less than 50k. The two small changes reduce the amount of
data transferred by about two-thirds. This reduces network traffic and the time it takes for the application to
initialize and show the data.
18
AppointmentMappingCollection mappings =
[Link];
[Link] = "Id";
[Link] = "Subject";
[Link] = "Body";
[Link] = "End";
[Link] = "Start";
[Link] = "Location";
[Link] = "Properties";
...
// set C1Scheduler data source to DataTable loaded from server
[Link] = _dtAppointments;
When users add, remove or edit appointments, all changes are propagated to the underlying DataTable
automatically by C1Scheduler – no code required.
Conclusion
It is possible to build data-centric Silverlight applications today, using familiar tools and techniques. The
introduction of new data technologies does not mean [Link] developers have to throw out all their knowledge,
tools, and existing code. In most cases, these new technologies extend rather than replace the existing tools and
patterns.
[Link] provides a Silverlight implementation of a substantial subset of the [Link] classes. This
allows developers to:
Leverage their knowledge of [Link] and the rich object model it provides, while at the same time
enjoying the benefits of new technologies such as LINQ, WCF, and so on.
Exchange relational data between client and server in a simple and efficient way;
Write code that uses similar classes and object models on client and server. Server and client use the same
language, similar classes, and similar object models.
19