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

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

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

822 CHAPTER 23 ADO.NET PART II: THE DISCONNECTED LAYER

Figure 23-17. Selecting the type of data source

Note This step of the wizard also allows you to connect data that comes from an external XML web service or a custom business object within a separate .NET assembly.

The second step (which will differ slightly based on your selection in step 1) allows you to configure your database connection. If you have a database currently added to Server Explorer, you should find it automatically listed in the drop-down list. If this is not the case (or if you ever need to connect to a database you have not previously added to Server Explorer), click the New Connection button. Figure 23-18 shows the result of selecting the local instance of AutoLot.

The third step asks you to confirm that you wish to save your connection string within an external App.config file, and if so, the name to use within the <connectionStrings> element. Keep the default settings for this step of the wizard and click the Next button.

The final step of the wizard is where you are able to select the database objects that will be accounted for by the autogenerated DataSet and related data adapters. While you could select each of the data objects of the AutoLot database, here you will only concern yourself with the Inventory table. Given this, change the suggested name of the DataSet to InventoryDataSet (see Figure 23-19), check the Inventory table, and click the Finish button.

CHAPTER 23 ADO.NET PART II: THE DISCONNECTED LAYER

823

Figure 23-18. Selecting the AutoLot database

Figure 23-19. Selecting the Inventory table

824 CHAPTER 23 ADO.NET PART II: THE DISCONNECTED LAYER

Once you do so, you will notice the visual designer has been updated in a number of ways. Most noticeable is the fact that the DataGridView displays the schema of the Inventory table, as illustrated by the column headers. Also, on the bottom of the form designer (in a region dubbed the component tray), you will see three components: a DataSet component, a BindingSource component, and a TableAdapter component (see Figure 23-20).

Figure 23-20. Our Windows Forms project, after running the Data Source Configuration Wizard

At this point you can run your application, and lo and behold, the grid is filled with the records of the Inventory table, as shown in Figure 23-21.

Figure 23-21. A populated DataGridView—no manual coding required!

CHAPTER 23 ADO.NET PART II: THE DISCONNECTED LAYER

825

The App.config File and the Settings.Settings File

If you examine your Solution Explorer, you will find your project now contains an App.config file. If you open this file, you will notice the name attribute of the <connectionStrings> element used in previous examples:

<?xml version="1.0" encoding="utf-8" ?> <configuration>

<configSections>

</configSections>

<connectionStrings>

<add name="VisualDataGridViewApp.Properties.Settings.AutoLotConnectionString" connectionString=

"Data Source=(local)\SQLEXPRESS;

Initial Catalog=AutoLot;Integrated Security=True" providerName="System.Data.SqlClient" />

</connectionStrings>

</configuration>

Specifically, the lengthy "VisualDataGridViewApp.Properties.Settings. AutoLotConnectionString" value has been set as the name of the connection string. Even stranger is the fact that if you scan all of the generated code, you will not find any reference to the ConfigurationManager type to read the value from the <connectionStrings> element. However, you will find that the autogenerated data adapter object (which you will examine in more detail in just a moment) is constructed in part by calling the following private helper function:

private void InitConnection()

{

this._connection = new global::System.Data.SqlClient.SqlConnection(); this._connection.ConnectionString = global::

VisualDataGridViewApp.Properties.Settings.Default.AutoLotConnectionString;

}

As you can see, the ConnectionString property is set via a call to Settings.Default. As it turns out, every Visual Studio 2008 project type maintains a set of application-wide settings that are burned into your assembly as metadata when you compile the application. The short answer is that if you open your compiled application using reflector.exe (see Chapter 2), you can view this internal type (see Figure 23-22).

Given the previous point, it would be possible to deploy your application without shipping the *.config file, as the embedded value will be used by default if a client-side *.config file is not present.

Note The Visual Studio 2008 settings programming model is really quite interesting; however, full coverage is outside of the scope of this chapter (and this edition of the text, for that matter). If you are interested in learning more, look up the topic “Managing Application Settings” in the .NET Framework 3.5 SDK documentation.

826 CHAPTER 23 ADO.NET PART II: THE DISCONNECTED LAYER

Figure 23-22. The Settings object contains an embedded connection string value.

Examining the Generated DataSet

Now let’s take a look at some of the core aspects of this generated code. First of all, insert a new class diagram type into your project by selecting the project icon in Solution Explorer and clicking the View Class Diagram button. Notice that the wizard has created a new DataSet type based on your input, which in this case is named InventoryDataSet. As you can see, this class defines a handful of members, the most important of which is a property named Inventory (see Figure 23-23).

Figure 23-23. The Data Source Configuration Wizard created a strongly typed DataSet.

CHAPTER 23 ADO.NET PART II: THE DISCONNECTED LAYER

827

If you double-click the InventoryDataSet.xsd file within Solution Explorer, you will load the Visual Studio 2008 Dataset Designer (more details on this designer in just a bit). If you right-click anywhere within this designer and select the View Code option, you will notice a fairly empty partial class definition:

public partial class InventoryDataSet { partial class InventoryDataTable

{

}

}

The real action is taking place within the designer-maintained file, InventoryDataSet. Designer.cs. If you open this file using Solution Explorer, you will notice that InventoryDataSet is actually extending the DataSet class type. When you (or a wizard) create a class extending DataSet, you are building what is termed a strongly typed DataSet. One benefit of using strongly typed DataSet objects is that they contain a number of properties that map directly to the database tables names. Thus, rather than having to drill into the collection of tables using the Tables property, you can simply use the Inventory property. Consider the following partial code, commented for clarity:

// This is all designer-generated code!

public partial class InventoryDataSet : global::System.Data.DataSet

{

//A member variable of type InventoryDataTable. private InventoryDataTable tableInventory;

//Each constructor calls a helper method named InitClass(). public InventoryDataSet()

{

...

this.InitClass();

}

//InitClass() preps the DataSet and adds the InventoryDataTable

//to the Tables collection.

private void InitClass()

{

this.DataSetName = "InventoryDataSet"; this.Prefix = "";

this.Namespace = "http://tempuri.org/InventoryDataSet.xsd"; this.EnforceConstraints = true; this.SchemaSerializationMode =

global::System.Data.SchemaSerializationMode.IncludeSchema; this.tableInventory = new InventoryDataTable(); base.Tables.Add(this.tableInventory);

}

//The read-only Inventory property returns

//the InventoryDataTable member variable. public InventoryDataTable Inventory

{

get { return this.tableInventory; }

}

}

In addition to wrapping the details of maintaining a DataTable object, the designer-generated strongly typed DataSet could contain similar logic to expose any DataRelation objects (which we do not currently have) that represent the connections between each of the tables.

828 CHAPTER 23 ADO.NET PART II: THE DISCONNECTED LAYER

Examining the Generated DataTable and DataRow

In a similar fashion, the wizard created a strongly typed DataTable class and a strongly typed DataRow class, both of which have been nested within the InventoryDataSet class. The InventoryDataTable class (which is the same type as the member variable of the strongly typed DataSet we just examined) defines a set of properties that are based on the column names of the physical Inventory table (CarIDColumn, ColorColumn, MakeColumn, and PetNameColumn) as well as a custom indexer and a Count property to obtain the current number of records.

More interestingly, this strongly typed DataTable class defines a set of methods (see Figure 23-24) that allow you to insert, locate, and delete rows within the table using strongly typed members (an attractive alternative to manually navigating the Rows and Columns indexers).

Figure 23-24. The custom DataTable type

Note The strongly typed DataTable also defines a handful of events you can handle to monitor changes to your table data.

The custom DataRow type is far less exotic than the generated DataSet or DataTable. As shown in Figure 23-25, this class extends DataRow and exposes properties that map directly to the schema of the Inventory table (also be aware that the columns are appropriately typed).

CHAPTER 23 ADO.NET PART II: THE DISCONNECTED LAYER

829

Figure 23-25. The custom DataRow type

Examining the Generated Data Adapter

Having some strong typing for our disconnected types is a solid benefit of using the Data Source Configuration Wizard, given that adding strongly typed classes by hand would be tedious (but entirely possible). This same wizard was kind enough to generate a custom data adapter object that is able to fill and update the InventoryDataSet and InventoryDataTable class types (see Figure 23-26).

Figure 23-26. A customized data adapter that operates on the strongly typed types

830 CHAPTER 23 ADO.NET PART II: THE DISCONNECTED LAYER

The autogenerated InventoryTableAdapter type maintains a collection of SqlCommand objects, each of which has a fully populated set of SqlParameter objects (this alone is a massive time-saver). Furthermore, this custom data adapter provides a set of properties to extract the underlying connection, transaction, and data adapter objects, as well as a property to obtain an array representing each command type. The obvious benefit is you did not have to author the code!

Using the Generated Types in Code

If you were to examine the Load event handler of the form-derived type, you will find that the Fill() method of the custom data adapter is called upon startup, passing in the custom DataTable maintained by the custom DataSet:

private void MainForm_Load(object sender, EventArgs e)

{

this.inventoryTableAdapter.Fill(this.inventoryDataSet.Inventory);

}

You can use this same custom data adapter object to update changes to the grid. Update the UI of your form with a single Button control (named btnUpdateInventory). Handle the Click event, and author the following code within the event handler:

private void btnUpdateInventory_Click(object sender, EventArgs e)

{

//This will push any changes within the Inventory table back to

//the database for processing. this.inventoryTableAdapter.Update(this.inventoryDataSet.Inventory);

//Get fresh copy for grid. this.inventoryTableAdapter.Fill(this.inventoryDataSet.Inventory);

}

Run your application once again; add, delete, or update the records displayed in the grid; and click the Update button. When you run the program again, you will find your changes are present and accounted for.

Understand that you are able to make use of each of these strongly typed classes directly in your code, in (more or less) the same way you have been doing throughout this chapter. For example, assume you have updated your form with a new chunk of UI real estate (see Figure 23-27) that allows the user to enter a new record using a series of text boxes (granted, this is a bit redundant for this example, as the DataGridView will do so on your behalf).

Within the Click event handler of the new Button, you could author the following code:

private void btnAddRow_Click(object sender, EventArgs e)

{

// Get data from widgets

int id = int.Parse(txtCarID.Text); string make = txtMake.Text; string color = txtColor.Text; string petName = txtPetName.Text;

//Use custom adapter to add row. inventoryTableAdapter.Insert(id, make, color, petName);

//Refill table data. this.inventoryTableAdapter.Fill(this.inventoryDataSet.Inventory);

}

CHAPTER 23 ADO.NET PART II: THE DISCONNECTED LAYER

831

Figure 23-27. A simple update to the form type

Or, if you so choose, you can manually add a new row:

private void btnAddRow_Click(object sender, EventArgs e)

{

//Get new Row.

InventoryDataSet.InventoryRow newRow = inventoryDataSet.Inventory.NewInventoryRow();

newRow.CarID = int.Parse(txtCarID.Text); newRow.Make = txtMake.Text; newRow.Color = txtColor.Text; newRow.PetName = txtPetName.Text;

inventoryDataSet.Inventory.AddInventoryRow(newRow);

//Use custom adapter to add row. inventoryTableAdapter.Update(inventoryDataSet.Inventory);

//Refill table data. this.inventoryTableAdapter.Fill(this.inventoryDataSet.Inventory);

}

Source Code The VisualDataGridViewApp project is included under the Chapter 23 subdirectory.

Decoupling Autogenerated Code from the UI Layer

To close, allow me to point out that while the Data Source Configuration Wizard launched by the DataGridView has done a fantastic job of authoring a ton of grungy code on our behalf, the previous example hard-coded the data access logic directly within the user interface layer—a major design faux pas. Ideally, this sort of code belongs in our AutoLotDAL.dll assembly (or some other data