Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro CSharp 2008 And The .NET 3.5 Platform [eng]

.pdf
Скачиваний:
78
Добавлен:
16.08.2013
Размер:
22.5 Mб
Скачать

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.

C H A P T E R 2 4

Programming with the LINQ APIs

Now that you have spent the previous two chapters examining the ADO.NET programming model, we are in a position to return to the topic of Language Integrated Query (LINQ). Here, you will begin by examining the role of LINQ to ADO.NET. This particular term is used to describe two related facets of the LINQ programming model, specifically LINQ to DataSet and LINQ to SQL. As you would expect, these APIs allow you to apply LINQ queries to relational databases and ADO.NET DataSet objects.

The remainder of this chapter will examine the role of LINQ to XML. This aspect of LINQ not only allows you to extract data from an XML document using the expected set of query operators, but also enables you to load, save, and generate XML documents in an extremely straightforward manner (much more so than working with the types packaged in the System.Xml.dll assembly).

Note This chapter assumes you are already comfortable with the LINQ programming model as described in Chapter 14.

The Role of LINQ to ADO.NET

As explained in Chapter 14, LINQ is a programming model that allows programmers to build strongly typed query expressions that can be applied to a wide variety of data stores (arrays, collections, databases, XML documents). While it is true that you always use the same query operators regardless of the target of your LINQ query, the LINQ to ADO.NET API provides some additional types and infrastructure to enable LINQ/database integration.

As mentioned, LINQ to ADO.NET is a blanket term that describes two database-centric aspects of LINQ. First we have LINQ to DataSet. This API is essentially a set of extensions to the standard ADO.NET DataSet programming model that allows DataSets, DataTables, and DataRows to be a natural target for a LINQ query expression. Beyond using the types of System.Core.dll, LINQ to DataSet requires your projects to make use of auxiliary types within the System.Data.DataSetExtensions.dll assembly.

The second component of LINQ to ADO.NET is LINQ to SQL. This API allows you to interact with a relational database by abstracting away the underlying ADO.NET data types (connections, commands, data adapters, etc.) through the use of entity classes. Through these entity classes, you are able to represent relational data using an intuitive object model and manipulate the data using LINQ queries. The LINQ to SQL functionality is contained within the System.Data.Linq.dll assembly.

837

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.

CHAPTER 24 PROGRAMMING WITH THE LINQ APIS

839

Recall from Chapter 23 that we used strongly typed objects to allow us to author code such as the following:

static void AddRowWithTypedDataSet()

{

InventoryTableAdapter invDA = new InventoryTableAdapter(); AutoLotDataSet.InventoryDataTable inv = invDA.GetData(); inv.AddInventoryRow(999, "Ford", "Yellow", "Sal"); invDA.Update(inv);

}

While all of these approaches have their place, LINQ to DataSet provides yet another option to manipulate the contained data using LINQ query expressions. Out of the box, the ADO.NET DataSet (and related types such as DataTable and DataView) do not have the necessary infrastructure to be a direct target for a LINQ query. For example, the following method would result in a compile-time error:

static void LinqOverDataTable()

{

// Get a DataTable of data.

InventoryDALDisLayer dal = new InventoryDALDisLayer( @"Data Source=(local)\SQLEXPRESS;" +

"Initial Catalog=AutoLot;Integrated Security=True"); DataTable data = dal.GetAllInventory();

// Get cars with CarID > 5?

var moreData = from c in data where (int)c["CarID"] > 5 select c;

}

If you were to compile the LinqOverDataTable() method, the compiler would inform you that the DataTable type does provide a “query pattern implementation.” Similar to the process of applying LINQ queries to objects that do not implement IEnumerable<T> (such as the ArrayList), ADO.NET objects must be transformed into a compatible type. To understand how to do so requires examining the types of System.Data.DataSetExtensions.dll.

The Role of the DataSet Extensions

The System.Data.DataSetExtensions.dll assembly extends the System.Data namespace with a handful of new members (see Figure 24-1).

Figure 24-1. The System.Data.DataSetExtensions.dll assembly

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