Data binding depends on two classes in the
System.Data
namespace: DataView
and
DataViewManager
. These classes provide an important layer
of indirection between your data and its display format, allowing you
to apply sorts and filter rows without modifying the underlying
information—that is, to have different views on the same data.
ADO.NET binding is always provided through one of these objects.
Both ASP.NET and Windows Forms allow you to bind other types of
objects to controls, including custom classes, arrays, and some
collection types. However, ADO.NET binding always uses
DataView
and DataViewManager
,
so this chapter focuses on these two classes.
The
DataView
class acts as a view onto a single
DataTable
. When creating a
DataView
object, you specify the underlying
DataTable
in the constructor:
// Create a new DataView for the Customers table. DataView view = new DataView(ds.Tables["Customers"]);
Every DataTable
also provides a default
DataView
through the
DataTable.DefaultView
property:
// Obtain a reference to the default DataView for the Customers table. DataView view = ds.Tables["Customers"].DefaultView;
The DataViewManager
represents a view of an entire
DataSet
. As with the DataView
,
you can create a DataViewManager
manually, passing
in a reference to a DataSet
as a constructor
argument, or you can use the default
DataViewManager
provided through the
DataSet.DefaultViewManager
property.
The DataView
and
DataViewManager
provide three key features:
Sorting based on any column criteria
Filtering based on any combination of column values
Filtering based on the row state (such as deleted, inserted, and unchanged)
To make all this a little clearer, it helps to
consider a simple example with the Windows
DataGrid
control. In Example 12-1,
three tables are queried and added to a DataSet
.
By setting its DataSource
property, the
Customers
table is then bound to the
DataGrid
in a single highlighted line.
private void DataTest_Load(object sender, System.EventArgs e) { string connectionString = "Data Source=localhost;" + "Initial Catalog=Northwind;Integrated Security=SSPI"; string SQL = "SELECT * FROM Customers"; // Create ADO.NET objects. SqlConnection con = new SqlConnection(connectionString); SqlCommand com = new SqlCommand(SQL, con); SqlDataAdapter adapter = new SqlDataAdapter(com); DataSet ds = new DataSet("Northwind"); // Execute the command. try { con.Open(); adapter.Fill(ds, "Customers"); com.CommandText = "SELECT * FROM Products"; adapter.Fill(ds, "Products"); com.CommandText = "SELECT * FROM Suppliers"; adapter.Fill(ds, "Suppliers"); } catch (Exception err) { MessageBox.Show(err.ToString()); } finally { con.Close(); } // Show the customers table in the grid. dataGrid1.DataSource = ds.Tables["Customers"]; }
On the surface, it looks as though this code is binding the grid
directly to a DataTable
object. However, behind
the scenes, .NET retrieves the corresponding
DataTable.DefaultDataView
and uses that. You can
replace the highlighted line with the following equivalent syntax:
dataGrid1.DataSource = ds.Tables["Customers"].DefaultView;
Similarly, you can create a new DataView
object
and use it for the binding:
DataView view = new DataView(ds.Tables["Customers"]); dataGrid1.DataSource = view;
This technique is particularly useful if you want to display
different views of the same data in multiple controls. Figure 12-1 shows the result of binding the view. The
DataGrid
automatically creates a column for each
field in the table and displays all the data in the order it was
retrieved from the data source. By default, every column is the same
width, and the columns are arranged according to the order of fields
in the SELECT statement; you’ll learn how to
customize the view later in this chapter.
There is one reason why you should bind directly to the view rather
than use the table name. If you specify an invalid table name when
binding directly to a table, you don’t receive an
error; the DataGrid
just appears empty. However,
if you make the same mistake when binding to a view, you receive a
more informative NullReferenceException
.
The DataGrid
is the only Windows Forms control
that supports binding to an entire DataSet
as well
as a single table, although many other third-party controls follow
suit. When binding a DataSet
, .NET automatically
uses the corresponding DataViewManager
provided
through the DataSet.DefaultViewManager
property:
// Bind to the DefaultViewManager explicitly. dataGrid1.DataSource = ds.DefaultViewManager; // Bind to the DefaultViewManager implicitly. This code is equivalent. dataGrid1.DataSource = ds; // Bind to an identical DataViewManager you create manually. // This code has the same effect, but isn't exactly the same // (because it creates a new object). dataGrid1.DataSource = new DataViewManager(ds);
Figure 12-2 shows the initial appearance of a
DataGrid
when bound to a
DataSet
. A separate navigational link is provided
for every table in the DataSet
. When the user
clicks on one of these links, the corresponding table is shown, as in
Figure 12-2.
There is one important difference between the
DataViewManager
and the
DataView
approach, however. When you use the
navigational links to display a table, .NET doesn’t
use the DefaultView
to configure the appearance of
the that table. Instead, every DataViewManager
provides a collection of DataViewSetting
objects.
When the user navigates to a table through a
DataViewManager
, a new DataView
is created according to the settings in the corresponding
DataViewSetting
object.