
Pro CSharp 2008 And The .NET 3.5 Platform [eng]
.pdf
832CHAPTER 23 ■ ADO.NET PART II: THE DISCONNECTED LAYER
access library). However, you may wonder how to harvest the code generated via the DataGridView’s associated wizard in a Class Library project, given that there certainly is no form designer by default.
Thankfully, you can activate the data design tools of Visual Studio 2008 from any sort of project (UI based or otherwise) without the need to copy and paste massive amounts of code between projects. To illustrate some of your options, open your AutoLotDAL project once again and insert into your project a new DataSet type (named AutoLotDataSet) via the Project Add New Item menu option (see Figure 23-28).
Figure 23-28. Inserting a new DataSet
This will open a blank Dataset Designer surface. At this point, use Server Explorer to connect to a given database (you should already have a connection to AutoLot), and drag and drop each database object (here, I did not bother to drag over the CreditRisk table) you wish to generate onto the surface. In Figure 23-29, you can see each of the custom aspects of AutoLot are now accounted for.
If you look at the generated code, you will find a new batch of strongly typed DataSets, DataTables, and DataRows, and a custom data adapter object for each table. Because the AutoLotDataSet type contains code to fill and update all of the tables of the AutoLot database, the amount of code autogenerated is more than an eye-popping 3,000 lines! However, much of this is grungy infrastructure you can remain blissfully unaware of. As you can see in Figure 23-30, the AutoLotDataSet type is constructed in a way that is very close to the previous InventoryDataSet type.

CHAPTER 23 ■ ADO.NET PART II: THE DISCONNECTED LAYER |
833 |
Figure 23-29. Our custom strongly typed types, this time within a Class Library project
Figure 23-30. The AutoLotDataSet

834 CHAPTER 23 ■ ADO.NET PART II: THE DISCONNECTED LAYER
As well, you will find a custom data adapter object for each of the database objects you dragged onto the Dataset Designer surface as well as a helpful type named TableAdapterManager that provides a single entry point to each object (see Figure 23-31).
Figure 23-31. The autogenerated data adapter objects
■Source Code The AutoLotDAL (Part 3) project is included under the Chapter 23 subdirectory.
A UI Front End: MultitabledDataSetApp (Redux)
Using these autogenerated types is quite simple, provided you are comfortable working with the disconnected layer. The downloadable source code for this text contains a project named Multi- tabledDataSetApp-Redux, which, as the name implies, is an update to the MultitabledDataSetApp project you created earlier in this chapter.
Recall that the original example made use of a loosely typed DataSet and a batch of SqlDataAdapter types to move the table data to and fro. This updated version makes use of the third iteration of AutoLotDAL.dll and the wizard-generated types. While I won’t bother to list all of the code here (as it is more or less the same as the first iteration of this project), here are the highlights:
•You no longer need to manually author an App.config file or use the ConfigurationManager to obtain the connection string, as this is handled via the Settings object.
•You are now making use of the strongly typed classes within the AutoLotDAL and
AutoLotDAL.AutoLotDataSetTableAdapters namespaces.

CHAPTER 23 ■ ADO.NET PART II: THE DISCONNECTED LAYER |
835 |
•You are no longer required to manually create or configure the relationships between your tables, as the Dataset Designer has done so automatically.
Regarding the last bullet point, be aware that the names the Dataset Designer gave the table relationships are different from the names we gave to them in the first iteration of this project. Therefore, the btnGetOrderInfo_Click() method must be updated to use the correct relationship names (which can be seen on the designer surface of the Dataset Designer), for example:
private void btnGetOrderInfo_Click(object sender, System.EventArgs e)
{
...
// Need to update relationship name!
drsOrder = drsCust[0].GetChildRows(autoLotDS.Relations["FK_Orders_Customers"]);
...
// Need to update relationship name!
DataRow[] drsInv = drsOrder[0].GetParentRows(autoLotDS.Relations["FK_Orders_Inventory"]);
...
}
■Source Code The MultitabledDataSetApp-Redux project is included under the Chapter 23 subdirectory.
Summary
This chapter dove into the details of the disconnected layer of ADO.NET. As you have seen, the centerpiece of the disconnected layer is the DataSet. This type is an in-memory representation of any number of tables and any number of optional interrelationships, constraints, and expressions. The beauty of establishing relations on your local tables is that you are able to programmatically navigate between them while disconnected from the remote data store.
You also examined the role of the data adapter type in this chapter. Using this type (and the related SelectCommand, InsertCommand, UpdateCommand, and DeleteCommand properties), the adapter can resolve changes in the DataSet with the original data store. As well, you learned how to navigate the object model of a DataSet using the brute-force manual approach, as well as via strongly typed objects, typically generated by the Dataset Designer tools of Visual Studio 2008.



838 CHAPTER 24 ■ PROGRAMMING WITH THE LINQ APIS
■Note As of .NET 3.5, LINQ to SQL does not support a data provider factory model (see Chapter 22). Therefore, when using this API, your data must be contained within Microsoft SQL Server. The LINQ to DataSet API, however, is agnostic in nature, as the DataSet being manipulated can come from any relational database.
Programming with LINQ to DataSet
Recall from the previous chapter that the DataSet type is the centerpiece of the disconnected layer and is used to represent a cached copy of interrelated DataTable objects and (optionally) the relationships between them. On a related note, you may also recall that the data within a DataSet can be manipulated in three distinct manners:
•Indexers
•Data table readers
•Strongly typed data members
When you make use of the various indexers of the DataSet and DataTable type, you are able to interact with the contained data in a fairly straightforward but very loosely typed manner. Recall that this approach requires you to treat the data as a tabular block of cells. For example:
static void PrintDataWithIndxers(DataTable dt)
{
// 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();
}
}
The CreateDataReader() method of the DataTable type offers a second approach, where we are able to treat the data in the DataSet as a linear set of rows to be processed in a sequential manner:
static void PrintDataWithDataTableReader(DataTable dt)
{
// Get the DataTableReader type.
DataTableReader dtReader = dt.CreateDataReader(); while (dtReader.Read())
{
for (int i = 0; i < dtReader.FieldCount; i++)
{
Console.Write("{0}\t", dtReader.GetValue(i));
}
Console.WriteLine();
}
dtReader.Close();
}
Finally, using a strongly typed DataSet yields a code base that allows you to interact with data in the object using properties that map to the actual column names in the relational database.


840 CHAPTER 24 ■ PROGRAMMING WITH THE LINQ APIS
Far and away the two most useful members are DataTableExtensions and DataRowExtensions. As their names imply, these types extend the functionality of DataTable and DataRow using a set of extension methods. The other key type is TypedTableBaseExtensions, which defines extension methods that can be applied to strongly typed DataSet objects to make the internal DataTable objects LINQ aware. All of the remaining members within the System.Data.DataSetExtensions.dll assembly are pure infrastructure and not intended to be used directly in your code base.
Obtaining a LINQ-Compatible DataTable
To illustrate using the DataSet extensions, assume you have a new C# Console Application named LinqOverDataSet. Be aware that when you create projects that target .NET 3.5, you will automatically be given a reference to System.Core.dll and System.Data.DataSetExtensions.dll; however, for this example, add an additional assembly reference to the AutoLotDAL.dll assembly you created in Chapter 23, and update your initial code file with the following logic:
using System.Data;
using AutoLotDisconnectedLayer;
namespace LinqOverDataSet
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("***** LINQ over DataSet *****\n");
//Get a DataTable containing the current Inventory
//of the AutoLot database.
InventoryDALDisLayer dal = new InventoryDALDisLayer(
@"Data Source=(local)\SQLEXPRESS;Initial Catalog=AutoLot;" + "Integrated Security=True");
DataTable data = dal.GetAllInventory();
// Invoke the methods that follow here!
Console.ReadLine();
}
}
}
When you wish to transform an ADO.NET DataTable into a LINQ-compatible object, you simply need to call the AsEnumerable() extension method defined by the DataTableExtensions type. This will return to you an EnumerableRowCollection object, which contains a collection of DataRows. Using the EnumerableRowCollection type, you are then able to operate on each row as expected. By way of a simple example:
static void PrintAllCarIDs(DataTable data)
{
//Get enumerable version of DataTable.
EnumerableRowCollection enumData = data.AsEnumerable();
//Print the car ID values.
foreach (DataRow r in enumData) Console.WriteLine("Car ID = {0}", r["CarID"]);
}
Because EnumerableRowCollection implements IEnumerable<T>, it would also be permissible to capture the return value using either of these code statements:

CHAPTER 24 ■ PROGRAMMING WITH THE LINQ APIS |
841 |
//Store return value as IEnumerable<T>.
IEnumerable<DataRow> enumData = data.AsEnumerable();
//Store return value implicitly.
var enumData = data.AsEnumerable();
At this point, we have not actually applied a LINQ query; however, the point is that the enumData object is now able to be the target of a LINQ query expression. Do notice that the EnumerableRowCollection does indeed contain a collection of DataRow objects, as we are applying a type indexer against each subobject to print out the value of the CarID column.
In most cases, you will not need to declare a variable of type EnumerableRowCollection to hold the return value of AsEnumerable(). Rather, you can invoke this method from within the query expression itself. Here is a more interesting method, which obtains a projection of CarID/Makes from all entries in the DataTable where the CarID is greater than the value of 5:
static void ApplyLinqQuery(DataTable data)
{
//Project a new result set containing
//the ID/color for rows with a CarID > 5 var cars = from car in data.AsEnumerable()
where (int)car["CarID"] > 5
select new
{
ID = (int)car["CarID"], Color = (string)car["Color"]
};
Console.WriteLine("Cars with ID greater than 5:"); foreach (var item in cars)
{
Console.WriteLine("-> CarID = {0} is {1}", item.ID, item.Color);
}
}
The Role of the DataRowExtensions.Field<T>()
Extension Method
One undesirable aspect of the current LINQ query expression is that we are making use of numerous casting operations and DataRow indexers to gather the result set, which could result in runtime exceptions if we attempt to cast to an incompatible data type. To inject some strong typing into our query, we can make use of the Field<T>() extension method of the DataRow type. By doing so, we increase the type safety of our query, as the compatibility of data types is checked at compile time. Consider the following update:
var cars = from car in data.AsEnumerable() where
car.Field<int>("CarID") > 5 select new
{
ID = car.Field<int>("CarID"), Color = car.Field<string>("Color")
};
Notice in this case we are able to invoke Field<T>() and specify a type parameter to represent the underlying data type of the column. As an argument to this method, we pass in the column