Managing Data and Relationships: The DataSet Class C# Help

The DataSet has been designed as an offline container of data. It has no notion of database connections, In fact, the data held within a DataSet does not necessarily need to have come from a database – it could just as easily be records from a CSV file, or points read from a measuring device.

ADataSet class consists of a set of data tables, each of which will have a set of data columns and data rows (see Figure 26-4), In addition to defining the data, you can also define links between tables within the DataSet class, One common scenario would be when defining a parent-child relationship commonly known as master/detail), One record in a table (say Order) links to many records in another table (say Order_Details). This relationship can be defined and navigated within the DataSet.

Figure 26-4

Figure 26-4

The following sections describe the classes that are used with a DataSet class.

Data Tables
A data table is very similar to a physical database table – it consists of a set of columns with particular properties and might have zero or more rows of data, A data table might also define a primary key, which can be one or more columns, and might also contain constraints on columns, The generic term for this information used throughout the rest of the chapter is schema.

Several ways exist to define the schema for a particular data table (and indeed the DataSet class as a whole), These are discussed after introducing data columns and data rows. Figure 26-5 shows some of the objects that are accessible through the data table.

Figure 26-5

Figure 26-5

A DataTable object (and also a DataColumn) can have an arbitrary number of extended properties associated with it, This collection can be populated with any user-defined information pertaining to the object, For example, a given column might have an input mask used to validate the contents of that column – a typical example is the U.S Social Security number, Extended properties are especially useful when the data is constructed within a middle tier and returned to the client for some processing, You could, for example, store validation criteria (such as min and max) for numeric columns in extended properties and use this in the UI tier when validating user input.

When a data table has been populated – by selecting data from a database, reading data from a file, or manually populating within code – the Rows collection will contain this retrieved data.

The Columns collection contains DataColumn instances that have been added to this table, These define the schema of the data, such as the data type, nullability, default values, and so on, The Constraints collection can be populated with either unique or primary key constraints.

One example of where the schema information for a data table is used is when displaying that data in a DataGrid, The DataGrid control uses properties such as the data type of the column to decide what control to use for that column, A bit field within the database will be displayed as a check box within the DataGrid, If a column is defined within then database schema as NOT NULL,this fact will be stored within the DataColumn so that it can be tested when the user attempts to move off a row.

Data Columns

A DataColumn object defines properties of a column within the DataTable, such as the data type of that column, whether the column is read-only, and various other facts. A column can be createa in code, or can be automatically generated by the runtime.

When creating a column, it is also useful to give it a name; otherwise, the runtime will generate a name for you in the form Columnn where n is an incrementing number.

The·aata type of the column can be set either by supplying it in the constructor or by setting the Date type property, Once you have loaded data into a data table you cannot alter the type of a column – you will just receive an ArgumentException.

Data columns can be created to hold the following .NET Framework data types:

Once created, the next thing to do with a DataColumn object is to set up other properties, such as the nullability of the column or the default value, The following code fragment shows a few of the more common options to set on a DataColumn object:

The following table shows the properties that can be set on a DataColumn object.

Data Rows

This class makes up the other part of the DataTable class. The columns within a data table are defined in terms of the DataColumn class. The actual data within the table is accessed using the DataRow object, The following example shows how to access rows within a data table. First, the connection details:

The following code introduces the SqlDataAdapter class, which is used to place data into a DataSet class, SqlDataAdapter issues the SQL clause and fills a table in the DataSet class called Customers with the output of the following query.

SqlDataAdapter da = new SqlDataAdapter(select, conn);
DataSet ds = new DataSet();
da.Fill(ds, “Customers”);

In the following code, you might notice the use of the DataRow indexer to access values from within that row. The value for a given column can be retrieved using one of the several overloaded indexers, These permit you to retrieve a value knowing the column number, name, or DataColumn:

foreach(DataRow row in ds.Tables[“Customers”] .Rows)
Console.WriteLine(“‘{O}’ from {1}” , row[O] ,row[1]);

One of the most appealing aspects of DataRow is that it is versioned. This permits you to receive various values for a given column in a particular row, The versions are described in the following table.

The version of a given column could be used in many ways. One example is when updating rows within the database, in which instance it is common to issue a SQLstatement such as the following:

SET Name= Column.Current
WHEREProductID = xxx
AND Name= Column.Original;

Obviously, this code would never compile, but it shows one use for original and current values of a column within a row.

To retrieve a versioned value from the DataRow indexer, use one of the indexer methods that accepts a DataRowVersion value as a parameter, The following snippet shows how to obtain all values of each column in a DataTable object:

The whole row has a state flag called RowState, which can be used to determine what operation is needed on the row when it is persisted back to the database, The RowState property is set to keep track of all the changes made to the DataTable, such as adding new rows, deleting existing rows, and changing columns within the table. When the data is reconciled with the database, the row state flag is used to determine what SQL operations should occur. The following table provides an overview of the flags that are defined by the DataRowState enumeration.

 The state of the row depends also on what methods have been called on the row, The AcceptChanges () method is generally called after successfully updating the data source (that is after persisting changes to the database).

The most common way to alter data in a DataRow is to use the indexer; however, if you have a number of changes to make, you also need to consider the BeginEdit () and EndEdit () methods.

When an alteration is made to a column within a DataRow, the ColumnChanging event is raised on the row’s DataTable, This permits you to override the ProposedValue property of the DataColumnChangeEventArgs class, and change it as required, This is one way of performing some data validation on column values, If you call BeginEdit ( ) before making changes, the ColumnChanging event will not be raised, This permits you to make multiple changes and then call EndEdit () to persist these changes, If you want to revert to the original values, call CancelEdit ( ).

A DataRow can be linked in some way to other rows of data, This permits the creation of navigable links between rows, Which is common in master/detail scenarios, The DataRow contains a GetChiIdRows ( ) method that will return an array of associated rows from another table in the same DataSet as the current row.

Schema Generation

You can create the schema for a DataTable in three ways:

¤ Let the runtime do it for you.

¤ Write code to create the table(s).

¤ Use the XML schema generator.

Runtime Schema Generation

The DataRow example shown earlier presented the following code for selecting data from a database and populating a DataSet class:

SqlDataAdapter da = new SqlDataAdapter(select , conn);
DataSet ds = new DataSet();
da.Fill(ds, “Customers”);

This is obviously easy to use, but it has a few drawbacks as well, For example, you have to make do with the default column names, which might work for you, but in certain instances, you might want to rename a physical database column (say PKID) to something more user-friendly.

You could naturally alias columns within your SOl clause, as in SELECT PID AS PersonID FROM personTable; it’s best to not rename columns within SQL, though; because a column only really needs to have a “pretty” name onscreen.

Another potential problem with automated DataTable/DataColurnn generation is that you have no control over the column types that the runtime chooses for your data, It does a fairly good job of deciding the correct data type for you, but as usual there are instances where you need more control, For example, you might have defined an enumerated type for a given column to simplify user code written against your class, If you accept the default column types that the runtime generates, the column will likely be an integer with a 32-bit range, as opposed to an enum with your predefined options.

Last, and probably most problematic, is that when using automated table generation, you have no type-safe access to the data within the DataTable – you are at the mercy of indexers, which return instances of object; rather than derived data types If you like sprinkling your code with typecast expressions, skip the following sections.

Hand-Coded Schema

Ceneranng the code to create a DataTable, replete with associated DataColumns, is fairly easy, The examples within this section access the Products table from the Northwind database shown in Figure 26-6.

Figure 26-6

Figure 26-6

The following code manufactures a DataTable, which corresponds to the schema shown in Figure 26-6 (but does not cover the nullability of columns):

You can alter the code in the DataRow example to use this newly generated table definition as follows:

The ManufactureProductDataTable ()method creates a new DataTable, adds each column in turn, and finally appends this to the list of tables within the DataSet, The DataSet has an indexer that takes the name of the table and returns that DataTable to the caller.

The previous example is still not really type-safe because indexers are being used on columns to retrieve the data, What would be better is a class (or set of classes) derived from DataSet, DataTabIe, and DataRow that defines type-safe accessors for tables; rows, and columns. You can generate this code yourself; it is not particularly tedious and you end up with truly type-safe data access classes.

If you don’t like generating these type-safe classes yourself, help is at hand, The .NET Framework includes support for the third method listed at the start of this section: using XMLschemas to define a DataSet class, a DataTable class, and the other classes that we have described here.

Data Relationships

When writing an application, it is often necessary to obtain and cache various tables of information, The DataSet class is the container for this information, With regular OLE DB, it was necessary to provide a strange SQL dialect to enforce hierarchical data relationships, and the provider itself was not without its own subtle quirks.

The DataSet class, however, has been designed from the start to establish relationships between data tables with ease, The code in this section shows how to generate manually and populate two tables with data So, if you don’t have access to SQL Server or the Northwind database, you can run this example anyway:

The tables used in this example are shown in Figure 26-7, They contain a primary key and name field, with the Room table having BuildingID as a foreign key.

Figure 26-7

Figure 26-7

These tables have been kept deliberately simple. The following code shows how to iterate through the rows in the Building table and traverse the relationship to list all of the child rows from the Room table:

The key difference between the DataSet class and the old-style hierarchical Recordset object is in the way the relationship is presented, In a hierarchical Recordset object, the relationship was presented as a pseudo-column within the row. This column itself was a Recordset object that could be iterated through, Under AOO.NET, however, a relationship is traversed simply by calling the GetChildRows () method:

DataRow[ ] children = theBuilding.GetChildRows(“Rooms”);

This method has a number of forms, but the preceding simple example uses just the name of the relationship to traverse between parent and child rows. It returns an array of rows that can be updated an appropriate by using the indexers, as shown in earlier examples.

What’s more interesting with data relationships is that they can be traversed both ways, Not only can you go from a parent to the child rows, but you can also find a parent row (or rows) from a child record simply by using the ParentRelations property on the DataTable class, This property returns a DataRelationCollection, which can be indexed using the [] array syntax (for example, ParentRelations [“Rooms”]), or as an alternative, the GetParentRows () method can be called, as shown here:

Two methods with various overrides are available for retrieving the parent row(s): GetParentRows () (which returns an array of zero or more rows) and GetParentRow () (which retrieves a single parent row given a relationship).

Data Constraints

Changing the data type of columns created on the client is not the only thing a DataTable is good for ADO.NET permits you to create a set of constraints on a column (or columns), which are then used to enforce rules within the data.

The following table lists the constraint types that are currently supported by the runtime, embodied as classes in the Sys tern. Data namespace.

Setting a Primary Key

As is common with a table in a relational database, you can supply a primary key, which can be base 1 on one or more columns from the DataTable, The following code creates a primary key for the Products table, whose schema was constructed by hand earlier.

Note that a primary key on a table is just one form of constraint. When a primary key is added to a DataTable, the runtime also generates a unique constraint over the key column(s), This is because there isn’t actually a constraint type of PrimaryKey – a primary key is simply a unique constraint over one or more columns.

Because a primary key can contain several columns, it is typed as an array of DataColumns. A table’s primary key can be set to those columns simply by assigning an array of columns to the property, To check the constraints for a table, you can iterate through the ConstraintCollection. For the autogenerated constraint produced by the preceding code, the name of the constraint is Constraint, That’s not a very useful name, so to avoid this problem it is always best to create the constraint in code first, then define which column(s) make up the primary key.

The following code names the constraint before creating the primary key:

Unique constraints can be applied to as many columns as you want.

Setting a Foreign Key

In addition to unique constraints, a DataTable class can also contain foreign key constraints, These are primarily used to enforce master/detail relationships but can also be used to replicate columns between tables if you set up the constraint correctly. A master / detail relationship is one where there is commonly one paren record (say an order) and many child records (order lines), linked by the primary key of the parent record.

A foreign key constraint can operate only over tables within the same DataSet, so the following example uses the Categories table from the Northwind database (shown in Figure 26-8), and assigns a constraint between it and the Products table.

Figure 26-8

Figure 26-8

The first step is to generate a new data table for the Categories table:

The last line of this code creates the primary key for the Categories table, The primary key in this instance is a single column; however, it is possible to generate a key over multiple columns using the array syntax shown.

Then the constraint can be created between the two tables:

This constraint applies to the link between Categories. CategoryID and Products. CategoryID, There are four different ForeignKeyConstraints – use those that permit you to name the constraint.

Setting Update and Delete Constraints

In addition to defining that there is some type of constraint between parent and child tables, you can define what should happen when a column in the constraint is updated.

The previous example sets the update rule and the delete rule, These rules are used when an action occurs to a column (or row) within the parent table, and the rule is used to decide what should happen to the row(s) within the child table that could be affected, Four different rules can be applied through the Rule enumeration:

¤ Cascade – If the parent key has been updated, copy the new key value to all child records, If the parent record has been deleted, delete the child records also This is the default option.

¤ None – No action whatsoever, This option leaves orphaned rows within the child data table.

¤ SetDefault – Each child record affected has the foreign key column(s) set to its default value, if one has been defined.

¤ SetNull – All child rows have the key column(s) set to DBNull, (Following the naming convention that Microsoft uses, this should really be SetDBNull.)

Constraints are enforced only within a DataSet class if the EnforceConstraints property of the DataSet is true.

This section has covered the main classes that make up the constituent parts of the DataSet class and has shown how to manually generate each of these classes in code, You can also define a DataTablc, DataRow, DataColumn, DataRelation, and Constraint using the XML schema file(s) and the XSD tool that ships with .NET.

The following section describes how to set up a simple schema and generate typesafe classes to access your data.

Posted on October 30, 2015 in Data Access

Share the Story

Back to Top