
Pro CSharp And The .NET 2.0 Platform (2005) [eng]
.pdf

CHAPTER 22 ■ DATABASE ACCESS WITH ADO.NET |
795 |
Table 22-9. Properties of the Mighty DataSet
Property |
Meaning in Life |
CaseSensitive |
Indicates whether string comparisons in DataTable objects are case |
|
sensitive (or not). |
DataSetName |
Represents the friendly name of this DataSet. Typically this value is |
|
established as a constructor parameter. |
EnforceConstraints |
Gets or sets a value indicating whether constraint rules are followed |
|
when attempting any update operation. |
HasErrors |
Gets a value indicating whether there are errors in any of the rows in |
|
any of the DataTables of the DataSet. |
RemotingFormat |
This new .NET 2.0 property allows you to define how the DataSet |
|
should serialize its content (binary or XML) for the .NET remoting layer. |
|
|
The methods of the DataSet mimic some of the functionality provided by the aforementioned properties. In addition to interacting with XML streams, the DataSet provides methods that allow you to copy/clone the contents of your DataSet, as well as establish the beginning and ending points of a batch of updates. Table 22-10 describes some core methods.
Table 22-10. Methods of the Mighty DataSet
Methods |
Meaning in Life |
AcceptChanges() |
Commits all the changes made to this DataSet since it was loaded or |
|
the last time AcceptChanges() was called. |
Clear() |
Completely clears the DataSet data by removing every row in each |
|
DataTable. |
Clone() |
Clones the structure of the DataSet, including all DataTables, as well as |
|
all relations and any constraints. |
Copy() |
Copies both the structure and data for this DataSet. |
GetChanges() |
Returns a copy of the DataSet containing all changes made to it since it |
|
was last loaded or since AcceptChanges() was called. |
GetChildRelations() |
Returns the collection of child relations that belong to a specified table. |
GetParentRelations() |
Gets the collection of parent relations that belong to a specified table. |
HasChanges() |
Overloaded. Gets a value indicating whether the DataSet has changes, |
|
including new, deleted, or modified rows. |
Merge() |
Overloaded. Merges this DataSet with a specified DataSet. |
ReadXml() |
Allow you to read XML data from a valid stream (file based, memory |
ReadXmlSchema() |
based, or network based) into the DataSet. |
RejectChanges() |
Rolls back all the changes made to this DataSet since it was created or |
|
the last time DataSet.AcceptChanges was called. |
WriteXml() |
Allow you to write out the contents of a DataSet into a valid stream. |
WriteXmlSchema() |
|
|
|
Now that you have a better understanding of the role of the DataSet (and some idea of what you can do with one), create a new console application named SimpleDataSet. Within the Main() method, define a new DataSet object that contains two extended properties representing your company name and timestamp (don’t forget to “use” System.Data):

796CHAPTER 22 ■ DATABASE ACCESS WITH ADO.NET
class Program
{
static void Main(string[] args)
{
Console.WriteLine("***** Fun with DataSets *****\n");
// Create the DataSet object.
DataSet carsInventoryDS = new DataSet("Car Inventory"); carsInventoryDS.ExtendedProperties["TimeStamp"] = DateTime.Now; carsInventoryDS.ExtendedProperties["Company"] = "Intertech Training";
}
}
A DataSet without DataTables is a bit like a workweek without a weekend. Therefore, the next task is to examine the internal composition of the DataTable, beginning with the DataColumn type.
Working with DataColumns
The DataColumn type represents a single column within a DataTable. Collectively speaking, the set of all DataColumn types bound to a given DataTable represents the foundation of a table’s schema information. For example, if you were to model the Inventory table of the Cars database, you would create four DataColumns, one for each column (CarID, Make, Color, and PetName). Once you have created your DataColumn objects, they are typically added into the columns collection of the DataTable type (via the Columns property).
If you have a background in relational database theory, you know that a given column in a data table can be assigned a set of constraints (e.g., configured as a primary key, assigned a default value, configured to contain read-only information, etc.). Also, every column in a table must map to an underlying data type. For example, the Inventory table’s schema requires that the CarID column map to an integer, while Make, Color, and PetName map to an array of characters. The DataColumn class has numerous properties that allow you to configure these very things. Table 22-11 provides a rundown of some core properties.
Table 22-11. Properties of the DataColumn
Properties |
Meaning in Life |
AllowDBNull |
This property is used to indicate if a row can specify null values in this |
|
column. The default value is true. |
AutoIncrement |
These properties are used to configure the autoincrement behavior for |
AutoIncrementSeed |
a given column. This can be helpful when you wish to ensure unique |
AutoIncrementStep |
values in a given DataColumn (such as a primary key). By default, a DataColumn |
|
does not support autoincrement behavior |
Caption |
This property gets or sets the caption to be displayed for this column (e.g., |
|
what the end user sees in a DataGridView). |
ColumnMapping |
This property determines how a DataColumn is represented when a DataSet |
|
is saved as an XML document using the DataSet.WriteXml() method. |
ColumnName |
This property gets or sets the name of the column in the Columns collection |
|
(meaning how it is represented internally by the DataTable). If you do not |
|
set the ColumnName explicitly, the default values are Column with (n+1) |
|
numerical suffixes (i.e., Column1, Column2, Column3, etc.). |
DataType |
This property defines the data type (Boolean, string, float, etc.) stored in |
|
the column. |
DefaultValue |
This property gets or sets the default value assigned to this column when |
|
inserting new rows. This is used if not otherwise specified. |

CHAPTER 22 ■ DATABASE ACCESS WITH ADO.NET |
797 |
Properties |
Meaning in Life |
Expression |
This property gets or sets the expression used to filter rows, calculate |
|
a column’s value, or create an aggregate column. |
Ordinal |
This property gets the numerical position of the column in the Columns |
|
collection maintained by the DataTable. |
ReadOnly |
This property determines if this column can be modified once a row has |
|
been added to the table. The default is false. |
Table |
This property gets the DataTable that contains this DataColumn. |
Unique |
This property gets or sets a value indicating whether the values in each |
|
row of the column must be unique or if repeating values are permissible. If |
|
a column is assigned a primary key constraint, the Unique property should |
|
be set to true. |
|
|
Building a DataColumn
To continue with the SimpleDataSet project (and illustrate the use of the DataColumn), assume you wish to model the columns of the Inventory table. Given that the CarID column will be the table’s primary key, you will configure the DataColumn object as read-only, unique, and non-null (using the ReadOnly, Unique, and AllowDBNull properties). Update the Main() method to build four DataColumn objects:
static void Main(string[] args)
{
...
//Create data columns that map to the
//'real' columns in the Inventory table
//of the Cars database.
DataColumn carIDColumn = new DataColumn("CarID", typeof(int)); carIDColumn.Caption = "Car ID";
carIDColumn.ReadOnly = true; carIDColumn.AllowDBNull = false; carIDColumn.Unique = true;
DataColumn carMakeColumn = new DataColumn("Make", typeof(string)); DataColumn carColorColumn = new DataColumn("Color", typeof(string)); DataColumn carPetNameColumn = new DataColumn("PetName", typeof(string)); carPetNameColumn.Caption = "Pet Name";
}
Enabling Autoincrementing Fields
One aspect of the DataColumn you may choose to configure is its ability to autoincrement. Simply put, autoincrementing columns are used to ensure that when a new row is added to a given table, the value of this column is assigned automatically, based on the current step of the incrementation. This can be helpful when you wish to ensure that a column has no repeating values (such as a primary key).
This behavior is controlled using the AutoIncrement, AutoIncrementSeed, and AutoIncrementStep properties. The seed value is used to mark the starting value of the column, whereas the step value identifies the number to add to the seed when incrementing. Consider the following update to the construction of the carIDColumn DataColumn:
static void Main(string[] args)
{
...
DataColumn carIDColumn = new DataColumn("CarID", typeof(int));

798 CHAPTER 22 ■ DATABASE ACCESS WITH ADO.NET
carIDColumn.ReadOnly = true; carIDColumn.Caption = "Car ID"; carIDColumn.AllowDBNull = false; carIDColumn.Unique = true; carIDColumn.AutoIncrement = true; carIDColumn.AutoIncrementSeed = 0; carIDColumn.AutoIncrementStep = 1;
}
Here, the carIDColumn object has been configured to ensure that as rows are added to the respective table, the value for this column is incremented by 1. Because the seed has been set at 0, this column would be numbered 0, 1, 2, 3, and so forth.
Adding a DataColumn to a DataTable
The DataColumn type does not typically exist as a stand-alone entity, but is instead inserted into a related DataTable. To illustrate, create a new DataTable type (fully detailed in just a moment) and insert each DataColumn object in the columns collection using the Columns property:
static void Main(string[] args)
{
...
// Now add DataColumns to a DataTable.
DataTable inventoryTable = new DataTable("Inventory"); inventoryTable.Columns.AddRange(new DataColumn[]
{ carIDColumn, carMakeColumn, carColorColumn, carPetNameColumn });
}
Working with DataRows
As you have seen, a collection of DataColumn objects represents the schema of a DataTable. In contrast, a collection of DataRow types represents the actual data in the table. Thus, if you have 20 listings in the Inventory table of the Cars database, you can represent these records using 20 DataRow types. Using the members of the DataRow class, you are able to insert, remove, evaluate, and manipulate the values in the table. Table 22-12 documents some (but not all) of the members of the DataRow type.
Table 22-12. Key Members of the DataRow Type
Members |
Meaning in Life |
HasErrors |
The HasErrors property returns a Boolean value indicating if there are |
GetColumnsInError() |
errors. |
GetColumnError() |
If so, the GetColumnsInError() method can be used to obtain the |
ClearErrors() |
offending members, and GetColumnError() can be used to obtain the |
RowError |
error description, while the ClearErrors() method removes each |
|
error listing for the row. |
|
The RowError property allows you to configure a textual description of |
|
the error for a given row. |
ItemArray |
This property gets or sets all of the values for this row using an array of |
|
objects. |
RowState |
This property is used to pinpoint the current “state” of the DataRow |
|
using values of the RowState enumeration. |
Table |
This property is used to obtain a reference to the DataTable containing |
|
this DataRow. |
AcceptChanges() |
These methods commit or reject all changes made to this row since the |
RejectChanges() |
last time AcceptChanges() was called. |

CHAPTER 22 ■ DATABASE ACCESS WITH ADO.NET |
799 |
Members |
Meaning in Life |
BeginEdit() |
These methods begin, end, or cancel an edit operation on a DataRow |
EndEdit() |
object. |
CancelEdit() |
|
Delete() |
This method marks this row to be removed when the AcceptChanges() |
|
method is called. |
IsNull() |
This method gets a value indicating whether the specified column |
|
contains a null value. |
|
|
Working with a DataRow is a bit different from working with a DataColumn, because you cannot create a direct instance of this type; rather, you obtain a reference from a given DataTable. For example, assume you wish to insert two rows in the Inventory table. The DataTable.NewRow() method allows you to obtain the next slot in the table, at which point you can fill each column with new data via the type indexer, as shown here:
static void Main(string[] args)
{
...
// Now add some rows to the Inventory Table.
DataRow carRow = inventoryTable.NewRow(); carRow["Make"] = "BMW";
carRow["Color"] = "Black"; carRow["PetName"] = "Hamlet"; inventoryTable.Rows.Add(carRow);
carRow = inventoryTable.NewRow(); carRow["Make"] = "Saab"; carRow["Color"] = "Red"; carRow["PetName"] = "Sea Breeze"; inventoryTable.Rows.Add(carRow);
}
Notice how the DataRow class defines an indexer that can be used to gain access to a given DataColumn by numerical position as well as column name. At this point, you have a single DataTable containing two rows.
Understanding the DataRow.RowState Property
The RowState property is useful when you need to programmatically identify the set of all rows in a table that have changed, have been newly inserted, and so forth. This property may be assigned any value from the DataRowState enumeration, as shown in Table 22-13.
Table 22-13. Values of the DataRowState Enumeration
Value |
Meaning in Life |
Added |
The row has been added to a DataRowCollection, and AcceptChanges() has not |
|
been called. |
Deleted |
The row has been deleted via the Delete() method of the DataRow. |
Detached |
The row has been created but is not part of any DataRowCollection. A DataRow is in |
|
this state immediately after it has been created and before it is added to |
|
a collection, or if it has been removed from a collection. |
Modified |
The row has been modified, and AcceptChanges() has not been called. |
Unchanged |
The row has not changed since AcceptChanges() was last called. |
|
|

800 CHAPTER 22 ■ DATABASE ACCESS WITH ADO.NET
While you are programmatically manipulating the rows of a given DataTable, the RowState property is set automatically:
static void Main(string[] args)
{
...
DataRow carRow = inventoryTable.NewRow();
//Prints out: Row State is: Detatched.
Console.WriteLine("Row State is: {0}.", carRow.RowState); carRow["Make"] = "BMW";
carRow["Color"] = "Black"; carRow["PetName"] = "Hamlet"; inventoryTable.Rows.Add(carRow);
//Prints out: Row State is: Added.
Console.WriteLine("Row State is: {0}.", inventoryTable.Rows[0].RowState);
...
}
As you can see, the ADO.NET DataRow is smart enough to remember its current state of affairs. Given this, the owning DataTable is able to identify which rows have been modified. This is a key feature of the DataSet, as when it comes time to send updated information to the data store, only the modified data is submitted.
Working with DataTables
The DataTable defines a good number of members, many of which are identical in name and functionality to those of the DataSet. Table 22-14 describes some core properties of the DataTable type beyond Rows and Columns.
Table 22-14. Key Members of the DataTable Type
Property |
Meaning in Life |
CaseSensitive |
Indicates whether string comparisons within the table are case sensitive (or |
|
not). The default value is false. |
ChildRelations |
Returns the collection of child relations for this DataTable (if any). |
Constraints |
Gets the collection of constraints maintained by the table. |
DataSet |
Gets the DataSet that contains this table (if any). |
DefaultView |
Gets a customized view of the table that may include a filtered view or |
|
a cursor position. |
MinimumCapacity |
Gets or sets the initial number of rows in this table (the default is 25). |
ParentRelations |
Gets the collection of parent relations for this DataTable. |
PrimaryKey |
Gets or sets an array of columns that function as primary keys for the data |
|
table. |
RemotingFormat |
Allows you to define how the DataSet should serialize its content (binary or |
|
XML) for the .NET remoting layer. This property is new in .NET 2.0. |
TableName |
Gets or sets the name of the table. This same property may also be specified |
|
as a constructor parameter. |
|
|

CHAPTER 22 ■ DATABASE ACCESS WITH ADO.NET |
801 |
For the current example, let’s set the PrimaryKey property of the DataTable to the carIDColumn DataColumn object:
static void Main(string[] args)
{
...
// Mark the primary key of this table.
inventoryTable.PrimaryKey = new DataColumn[] { inventoryTable.Columns[0] };
}
Once you do this, the DataTable example is complete. The final step is to insert your DataTable into the carsInventoryDS DataSet object. Then you’ll pass your DataSet to a (yet to be written) helper method named PrintDataSet():
static void Main(string[] args)
{
...
//Finally, add our table to the DataSet. carsInventoryDS.Tables.Add(inventoryTable);
//Now print the DataSet.
PrintDataSet(carsInventoryDS);
}
The PrintDataSet() method simply iterates over each DataTable in the DataSet, printing out the column names and row values using the type indexers:
static void PrintDataSet(DataSet ds)
{
Console.WriteLine("Tables in '{0}' DataSet.\n", ds.DataSetName); foreach (DataTable dt in ds.Tables)
{
Console.WriteLine("{0} Table.\n", dt.TableName);
// Print out the column names.
for (int curCol = 0; curCol < dt.Columns.Count; curCol++)
{
Console.Write(dt.Columns[curCol].ColumnName.Trim() + "\t");
} |
|
Console.WriteLine("\n---------------------------------- |
"); |
// Print the DataTable.
for (int curRow = 0; curRow < dt.Rows.Count; curRow++)
{
for (int curCol = 0; curCol < dt.Columns.Count; curCol++)
{
Console.Write(dt.Rows[curRow][curCol].ToString() + "\t");
}
Console.WriteLine();
}
}
}
Figure 22-12 shows the program’s output.

802 CHAPTER 22 ■ DATABASE ACCESS WITH ADO.NET
Figure 22-12. Contents of the example’s DataSet object
Working with .NET 2.0 DataTableReaders
DataTables provide a number of methods beyond what we’ve examined thus far. For example, like
DataSets, DataTables support AcceptChanges(), GetChanges(), Copy(), and ReadXml()/WriteXml() methods. As of .NET 2.0, DataTables also now support a method named CreateDataReader(). This method allows you to obtain the data within a DataTable using a data reader–like navigation scheme (forward-only, read-only). To illustrate, create a new helper function named PrintTable(), implemented as so:
private static void PrintTable(DataTable dt)
{
Console.WriteLine("\n***** Rows in DataTable *****");
//Get the new .NET 2.0 DataTableReader type.
DataTableReader dtReader = dt.CreateDataReader();
//The DataTableReader works just like the DataReader. while (dtReader.Read())
{
for (int i = 0; i < dtReader.FieldCount; i++)
{
Console.Write("{0} = {1} ", dtReader.GetName(i), dtReader.GetValue(i).ToString().Trim());
}
Console.WriteLine();
}
dtReader.Close();
}
Notice that the DataTableReader works identically to the data reader object of your data provider. Using a DataTableReader can be an ideal choice when you wish to quickly pump out the data within a DataTable without needing to traverse the internal row and column collections. To call this method, simply pass in the correct table:
static void Main(string[] args)
{
...
// Print out the DataTable via 'table reader'.
PrintTable(carsInventoryDS.Tables["Inventory"]);
}

CHAPTER 22 ■ DATABASE ACCESS WITH ADO.NET |
803 |
Persisting DataSets (and DataTables) As XML
To wrap up the current example, recall that DataSets and DataTables both support WriteXml() and ReadXml() methods. WriteXml() allows you to persist the object’s content to a local file (as well as into any System.IO.Stream-derived type) as an XML document. ReadXml() allows you to hydrate the state of a DataSet (or DataTable) from a given XML document. In addition, DataSets and DataTables both support WriteXmlSchema() and ReadXmlSchema() to save or load an *.xsd file. To test this out for yourself, update your Main() method with the final set of code statements:
static void Main(string[] args)
{
...
//Save this DataSet as XML. carsInventoryDS.WriteXml("carsDataSet.xml"); carsInventoryDS.WriteXmlSchema("carsDataSet.xsd");
//Clear out DataSet and print contents (which are empty). carsInventoryDS.Clear();
PrintDataSet(carsInventoryDS);
//Load and print the DataSet. carsInventoryDS.ReadXml("carsDataSet.xml"); PrintDataSet(carsInventoryDS);
}
If you open the carsDataSet.xml file, you will find that each column in the table has been encoded as an XML element:
<?xml version="1.0" standalone="yes"?> <Car_x0020_Inventory>
<Inventory>
<CarID>0</CarID>
<Make>BMW</Make>
<Color>Black</Color>
<PetName>Hamlet</PetName>
</Inventory>
<Inventory>
<CarID>1</CarID>
<Make>Saab</Make>
<Color>Red</Color> <PetName>Sea Breeze</PetName>
</Inventory> </Car_x0020_Inventory>
Finally, recall that the DataColumn type supports a property named ColumnMapping, which can be used to control how a column should be represented in XML. The default setting is MappingType.Element. However, if you establish the CarID column as an XML attribute as follows by updating your existing carIDColumn DataColumn object
static void Main(string[] args)
{
...
DataColumn carIDColumn = new DataColumn("CarID", typeof(int));
...
carIDColumn.ColumnMapping = MappingType.Attribute;
}
you will find the following XML: