You will learn how to use the DataAdapter Configuration Wizard to autogenerate code that initializes Command and DataAdapter objects for use with a specific table.. Again, Visual Studio
Trang 19.3 Use Visual Studio NET Tools to Speed Up Writing ADO.NET Code
The code you've written up to this point doesn't do that much It doesn't even access the database The next task is to write code that populates the class with data from the
database, and the first step in doing this is setting up database access objects
Technique
In Chapter 3, you learned how to fill a dataset to store data in a disconnected fashion In this chapter, you will use a strongly typed dataset-that is, a dataset with data rows that match the name and datatypes of the columns You will learn how to use the DataAdapter Configuration Wizard to autogenerate code that initializes Command and DataAdapter objects for use with a specific table
Steps
1 Right-click on your project and select Add New Item from the Add menu Choose DataSet and name it dsCustomers.xsd
2 Visual Studio NET opens dsCustomers.xsd in Design mode Expand the Server explorer and drill down to Data Connections, Northwind, Tables Drag the
Customers table onto the Design view
3 Visual Studio might process for a few seconds, but afterward, you'll have a
strongly typed dataset
The result? Instead of writing dataset code like this:
strCustomerID = CType(ds.Tables("Customers").Rows(0).Item("CustomerID"), String)
you'll have code that looks like this:
strCustomerID = ds2.Customers(0).CustomerID
A strongly typed dataset is a combination of two documents One is a vb file with the same name as the dataset Visual Studio NET will not show you the contents
of this file (unless you step into it while debugging), and the contents don't appear
in the Solution Explorer The other file is an xsd file, or a XML Schema
Definition (XSD), which defines a data structure The vb file reads the XSD to properly instantiate a strongly typed dataset
Trang 2You should always take a look at any code that is generated automatically by a tool because the tool might generate code that doesn't do precisely what you want
it to do If you have never seen an XSD, this is also a good time to learn something new Listing 9.18 shows the XSD for the dsCustomers dataset You can view the XSD you created by opening dsCustomers.xsd from the Solution Explorer and then clicking the XML button at the bottom of left corner of the screen
Listing 9.18 dsCustomers.xsd: The Customers XSD
<?xml version="1.0" encoding="utf-8" ?>
<xsd:schema id="dsCustomers"
targetNamespace="http://tempuri.org/dsCustomers.xsd"
elementFormDefault="qualified"
xmlns="http://tempuri.org/dsCustomers.xsd"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="dsCustomers" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="Customers">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="CustomerID" type="xsd:string" />
<xsd:element name="CompanyName" type="xsd:string" />
<xsd:element name="ContactName" type="xsd:string" minOccurs="0" /> <xsd:element name="ContactTitle" type="xsd:string" minOccurs="0" /> <xsd:element name="Address" type="xsd:string" minOccurs="0" /> <xsd:element name="City" type="xsd:string" minOccurs="0" />
<xsd:element name="Region" type="xsd:string" minOccurs="0" />
<xsd:element name="PostalCode" type="xsd:string" minOccurs="0" /> <xsd:element name="Country" type="xsd:string" minOccurs="0" /> <xsd:element name="Phone" type="xsd:string" minOccurs="0" />
<xsd:element name="Fax" type="xsd:string" minOccurs="0" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
<xsd:unique name="dsCustomersKey1" msdata:PrimaryKey="true">
<xsd:selector xpath=".//Customers" />
<xsd:field xpath="CustomerID" />
</xsd:unique>
</xsd:element>
Trang 3</xsd:schema>
It is beyond the scope of this chapter and this book to fully explain an XSD The subject requires an entire book of its own But it's important to point out a few areas of this XSD
The element tags are really nothing more than the properties you have already declared Each element has a name that corresponds to a column in the Customers table, as well as a type that loosely corresponds to the datatype of the column
The element tag also has a minOccurs attribute This attribute actually defines whether a value is required for that element The default for the minOccurs
attribute is 1, which means that the element does not allow Null values
Also, take a close look at the lines at the end of the XSD that begin with
<xsd:unique This XML block refers back to the CustomerID element, and it declares that that element is the primary key of the dsCustomers XSD
A dataset is just one part of what you need Without a data adapter, the dataset just sits there You could create all the data adapter code yourself, but again, Visual Studio NET can do a lot of the work for you
4 Right-click on your project and select Add New Item from the Add menu Choose Component Class and name it CCustomerData.vb
5 Visual Studio opens CCustomerData.vb in Design mode Expand the Toolbox on the left side of the screen Choose the Data tab, and drag an OleDbConnection onto the Design view
6 Click on OleDbConnection1 and rename it oleCnn
7 Set the ConnectionString property You should have an existing connection to the Northwind database (You set this up in section 9.1.) If you don't have an existing connection, create a connection by selecting New Connection from the
ConnectionString menu
8 Drag an OleDbDataAdapter from the Data tab of the Toolbox Doing so opens the DataAdapter Configuration Wizard
9 First, in the Choose Your Data Connection page of the wizard, shown in Figure 9.2, select a connection for the data adapter, and then click Next
Figure 9.2 The Choose Your Data Connection page of the DataAdapter
Configuration Wizard
Trang 410 Now, you have the option of choosing the type of query to use with the data
adapter For the purposes of this example, use SQL statements The other choices, shown in Figure 9.3, allow you to specify existing stored procedures, or have the wizard create new database stored procedures for you
Figure 9.3 The Choose a Query Type page of the DataAdapter Configuration
Wizard
Trang 511 The next page of the wizard, shown in Figure 9.4, asks for a SQL statement to retrieve data from the database Choose Query Builder to create a new query
Figure 9.4 The Generate the SQL Statements page of the DataAdapter
Configuration Wizard
Trang 612 Using the Query Builder, shown in Figure 9.5, select the Customers table, and return all the columns in the table Add a WHERE condition to select a single customer row based on the Customer ID as in Listing 9.19 Click Next and then Finish
Listing 9.19 CustomerData.vb: The SELECT Command Text to Use in the DataAdapter Configuration Wizard
SELECT CustomerID, CompanyName, ContactName, ContactTitle,
Address, City, Region, PostalCode, Country, Phone, Fax
FROM Customers
WHERE CustomerID = ?
Figure 9.5 The Query Builder allows you to graphically build a SQL
statement
Trang 713 Again, Visual Studio picks some undescriptive and bland names for the data
adapter and its Select, Update, Insert, and Delete commands In this example, OleDbAdapter1 will be renamed to odaCustomers, and OleDb[Action]Command1 will be renamed to [Action]Customer
14 Finally, right-click on odaCustomers and select Properties In the Properties
window, expand the TableMappings property, which will open a dialog box like that shown in Figure 9.6 This property tells Visual Studio NET where to look for datatype and column information The DataAdapter Configuration Wizard will have referenced the Customers table because it was not aware of the XSD you created earlier in this section Check on the Use a Dataset to Suggest Table and Column Names check box, and select dsCustomers from the DataSet combo box Make sure that the DataSet Table combo box references the Customers table of the dataset
Figure 9.6 The Table Mappings collection of the data adapter
Trang 8Based on the query you entered, Visual Studio NET will generate Update, Delete, and Insert SQL statements for the Customers table, as well as code that populates the
parameter collections and other properties of Command objects based on those SQL statements Most important, if you modify the SQL statements, Visual Studio NET updates the Visual Basic code setting up their command objects
The SQL statements that Visual Studio NET generates might be a bit more complex than you need In Listing 9.20, the UPDATE command has a WHERE condition that
references every column instead of just the primary key
Listing 9.20 CustomerData.vb: The Update Command Text from the Visual Studio NET-Generated Data Adapter
UPDATE Customers
SET CustomerID = ?, CompanyName = ?,
ContactName = ?, ContactTitle = ?,
Address = ?, City = ?,
Region = ?, PostalCode = ?,
Country = ?, Phone = ?,
Trang 9Fax = ?
WHERE (CustomerID = ?)
AND (CompanyName = ?)
AND (Address = ? OR ? IS NULL AND Address IS NULL)
AND (City = ? OR ? IS NULL AND City IS NULL)
AND (ContactName = ? OR ? IS NULL AND ContactName IS NULL)
AND (ContactTitle = ? OR ? IS NULL AND ContactTitle IS NULL)
AND (Country = ? OR ? IS NULL AND Country IS NULL)
AND (Fax = ? OR ? IS NULL AND Fax IS NULL)
AND (Phone = ? OR ? IS NULL AND Phone IS NULL)
AND (PostalCode = ? OR ? IS NULL AND PostalCode IS NULL)
AND (Region = ? OR ? IS NULL AND Region IS NULL)
This SQL statement is guaranteed to make sure you update only the record you retrieved
If the record has changed in another session since the data was retrieved by the current session, that row won't be updated This might be the behavior you need in some
situations Still, the whole point of a primary key is that it fully represents all the non-key values in the table (and, of course, none of the non-key values should rely on each other for definition), so perhaps that WHERE condition is overkill
You might also have noticed that the SET clause of the UPDATE statement that Visual Studio NET generated updates the value of the CustomerID column In the case of the Customers table, this SQL statement could not execute because DRI is enforcing the relationship with the Orders table However, not all databases use DRI; if the
CustomerID were changed in that scenario, the code would execute and orphan rows in other tables that contain the old CustomerID value
In that case, it would be best to modify the Visual Studio NET-generated SQL
statements to reflect the primary key and make the code more sensible For example, the UPDATE statement from Listing 9.20 would read as shown in Listing 9.21
You can access the Insert, Update, and Delete SQL statements used by the data adapter
by expanding the related command in the Properties window Then look for the
CommandText property and click on the Ellipsis button, as shown in Figure 9.7
Listing 9.21 CustomerData.vb: A Simpler Update Command Text from the Visual Studio NET-Generated Data Adapter
UPDATE Customers
SET CompanyName = ?,
ContactName = ?, ContactTitle = ?,
Address = ?, City = ?,
Region = ?, PostalCode = ?,
Trang 10Country = ?, Phone = ?,
Fax = ?
WHERE (CustomerID = ?)
Figure 9.7 The Properties window allows you to access the CommandText property
by expanding the data adapter's Update command
When you do modify those statements, you'll notice that a SELECT SQL statement follows the Insert and Update commands
In ADO 2.x, a cursor was maintained on the server using server resources or on the client with a connection to the server This meant that an updateable recordset would receive notification of data changes, such as the value of an IDENTITY or other
auto-incrementing column after inserting a new row or the values of updated data after an UPDATE trigger made additional changes The open connection and cursor, however, consume substantial resources and can adversely affect server performance ADO.NET's disconnected architecture is an attempt to resolve this resource problem Still, refreshing the dataset with the most current data can be quite helpful in databases that have triggers
or complex stored procedures, so this second SQL command will retrieve the updated
Trang 11data from the database and refresh the dataset to reflect the database-side changes to submitted data
Tip
If you're working with a Web application on SQL Server, you might not need this level of concurrency All you might need is the new IDENTITY value when inserting records Instead of selecting back the inserted row, you could end the SQL statement with this:
SELECT @@IDENTITY
which returns the last identity value created in the current transaction You execute the Insert command using the ExecuteScalar method, which returns this integer value to your Visual Basic code
This requires dropping the Visual Studio NET-generated data adapter code, so you need to weigh this slight performance gain against the time needed to maintain manually generated code However, as you'll see in the following pages of this section, the code that Visual Studio NET generates is nothing you haven't read about in the earlier chapters of this book
As mentioned earlier, before you start writing code that utilizes this data adapter, you should take a look at the code that Visual Studio NET has generated You can view the generated code, like the excerpts shown in Listing 9.22, by right-clicking on the gray area
of CCustomerData.vb and selecting View Code
Listing 9.22 CustomerData.vb: A Sample of the Code Generated by the Visual
Studio NET DataAdapter Configuration Wizard
Private Sub InitializeComponent()
' instantiate the connection object
Me.oleCnn = New System.Data.OleDb.OleDbConnection()
' instantiate the data adapter
Me.odaCustomers = New System.Data.OleDb.OleDbDataAdapter()
' instantiate the OLE DB command we will use for updating data
Me.updateCustomer = New System.Data.OleDb.OleDbCommand()
' set the table mapping properties for the data adapter This defines