Using Parameters with SQL Statements and Stored Procedures Dim oCon As New SqlConnectionsConnectDim oCmd As New SqlCommandsSQL, oCon Try ‘ specify query type, add parameter and open conn
Trang 1The text entered into the text box has added an extra test to the WHEREclause, and this test will
be truefor every row in the table; therefore, all the rows are returned If, for example, you werecollecting a username and password from a visitor and creating a SQL statement this way, youcould find that your system is open to attack from this type of value entered by a user Forexample, you might construct the SQL statement for such a process by using code like this:sSQL = “SELECT UserID FROM Users WHERE UserID = ‘“ & txtUser.Text
& “‘ AND Password = ‘“ & txtPassword.Text & “‘“
In theory, this will return a row only when the user ID and password match the entries in thedatabase However, by using the technique just demonstrated, a visitor could contrive to havethe following SQL statement executed:
SELECT UserID FROM Users WHERE UserID = ‘johndoe’
AND Password = ‘secret’ or ‘1’ = ‘1’
This would return a non-empty rowset, and if you only check whether there are any rowsreturned, you might find that your security has been breached
However, if you enter the same text into the second text box in the sample page and click thesecond Go button to execute the SQL string with the value as a parameter, you’ll see that norows are returned (see Figure 10.4)
FIGURE 10.4 The result of entering a
mali-cious string for the parameter
to a SQL statement
To understand why no rows are returned in this example, you can open the Profiler utility (byselecting Start, Microsoft SQL Server, Profiler) and trace the actions taken in the database In thiscase, this is the instruction that SQL Server executes:
exec sp_executesql N’SELECT CustomerID, CompanyName, City, Country FROM Customers WHERE CustomerID LIKE @CustomerID’,
N’@CustomerID nvarchar(4000)’, @CustomerID = N’’’ or ‘’1’’=’’1’
In other words, SQL Server is passing the SQL statement and the parameter separately to thesystem stored procedure named sp_executesql, and it is specifying that the parameter is a
Trang 2Using Parameters with SQL Statements and Stored Procedures
character string (nvarchar) This string doesnot match the value in the CustomerIDcolumn
of any row, so no rows are returned
It Gets Even Worse…
The problem with the literal construction ofthe SQL statement described in the precedingsection actually leaves you open to risks thatare even more serious than you might think
For example, if you enter the following textinto the first text box and execute it, you’llsee that the SQL statement shown in Figure10.5 is used:
‘; update customers set city=’here!’ where customerID like ‘BOLID
In fact, this is a batch statement that contains two separate SQL statements The first one fails to
find any rows that match the empty string in the WHEREclause, but then the second one is
executed, and it updates the table.
How to Use SQL Profiler
To use SQL Profiler, open it from the Startmenu or the Tools menu in EnterpriseManager and select File, New Trace toconnect to your database In the TraceProperties dialog that appears, select theEvents tab and make sure that the completeset of actions for the Stored Procedure entry
in the list of available TSQL event classes(displayed in the right-hand list) is selected
Then click Run, and you’ll see the statementsthat are being executed appear in the mainProfiler window
FIGURE 10.5 A malicious value that
updates the source table inthe database
If you now change the value in the first text box and display the rows for customers whose IDsstart with BO, you’ll see that the first one has been updated (see Figure 10.6) However, if you trythis with the second text box, you’ll find that—as before—the process has no effect on the origi-nal data The value that is entered and passed to SQL Server as a parameter simply fails to matchany existing rows in the table, and nothing is changed or returned
One consolation is that the attack could be worse For example, your malicious visitor couldhave entered this instead:
‘; drop database Northwind
This deletes the database altogether (The double hyphen at the end is a rem or comment marker
that forces SQL Server to ignore the final apostrophe that gets added to the statement batch.)
So if you construct SQL statements dynamically in your code and there’s any risk at all that thevalues you use might contain something other than you expect, you should always use parame-ters to build the SQL statement In fact, it’s not a bad idea to do it every time!
Trang 3The Code for Adding ParametersThe code used in the sample page shown in Figures 10.1 through 10.6 contains two routines—one for each of the two Go buttons in the page The first one builds the SQL statement in literalfashion, using the following:
Dim sSQL As String = “SELECT CustomerID, CompanyName, City, “ _
& “Country FROM Customers “ _
& “WHERE CustomerID LIKE ‘“ & sParam & “‘“
In this case, sParamis the value extracted from the first text box on the page This SQL statement
is then executed and the result is assigned to the DataSourceproperty of the DataGridcontrol onthe page in the usual way
The second routine, which runs when the second Go button is clicked, works a little differentlyfrom the first Listing 10.1 shows the complete routine After collecting the value from thesecond text box, the routine declares the SQL statement However, this time, the WHEREclausecontains a parameter named @CustomerID:
“WHERE CustomerID LIKE @CustomerID”
LISTING 10.1 A Routine to Execute a SQL Statement with a ParameterSub UseParamValue(sender As Object, e As EventArgs)
‘ get input value from TextBoxDim sParam As String = txtParam.Text
‘ declare SQL statement containing parameterDim sSQL As String = “SELECT CustomerID, CompanyName, City, “ _
& “Country FROM Customers “ _
& “WHERE CustomerID LIKE @CustomerID”
‘ get connection string, create connection and commandDim sConnect As String = _
ConfigurationSettings.AppSettings(“NorthwindSqlClientConnectString”)
FIGURE 10.6 The result of executing the
batch command shown inFigure 10.5
Trang 4Using Parameters with SQL Statements and Stored Procedures
Dim oCon As New SqlConnection(sConnect)Dim oCmd As New SqlCommand(sSQL, oCon)
Try
‘ specify query type, add parameter and open connectionoCmd.Parameters.Add(“@CustomerID”, sParam)
oCmd.CommandType = CommandType.TextoCon.Open()
‘ execute query and assign result to DataGriddgr1.DataSource = oCmd.ExecuteReader()dgr1.DataBind()
‘close connection afterwardsoCon.Close()
‘ display SQL statement in page and show hintlblResult.Text = “Executed: “ & sSQL
lblHint.Visible = True
Catch oErr As Exception
‘ be sure to close connection if error occurs
‘ can call Close more than once if required - no exception
‘ is generated if Connection is already closedoCon.Close()
lblResult.Text = “<font color=’red’><b>ERROR: </b>” _
& oErr.Message & “</font>”
End Try
End Sub
Next, you create the Connectioninstance and Commandinstance as usual Before executing the SQLstatement, however, you have to add a parameter to the Commandinstance to match the parame-ter declared within the SQL statement The sample code uses the simplest override of the Addmethod for the Parameterscollection of the Commandinstance and specifies the name and value ofthe parameter The data type of the variable is automatically used to set the data type of theparameter—in this case, a Stringdata type, which means that the parameter will be treated asbeing of type nvarchar(System.Data.SqlDbType.NVarChar) when SQL Server processes it
LISTING 10.1 Continued
Trang 5Notice also that you still have to use the valueTextfor the CommandTypeproperty of the Commandinstance because this is still a SQL statementand not a stored procedure (Textis the default,
so you could, in fact, omit it altogether.)
Ordering of Stored Procedures and Query Parameters
A parameter-related issue can cause problems
if you are not aware of it It concerns the waythat the different NET Framework dataproviders handle parameters when you specifythem by name The sample page shown inFigure 10.7 helps illustrate this issue
Parameter Name Prefixes in SQL Server
In databases other than SQL Server or Sybase
databases, you use just a question mark (?)
as the parameter placeholder If there is morethan one parameter, you use multiple ques-
tion mark placeholders and you must add the
parameters to the Parameterscollection ofthe Commandinstance in the same order thatthe placeholders appear in the SQL state-ment The names of the parameters are
ignored in this case You can use this same
syntax with SQL Server and Sybase as well,although the named parameter technique isusually more readable and less error prone
FIGURE 10.7The ordering of stored proce-dure parameters that are specified by name
The sample page uses two stored procedures—one in SQL Server and one in an Access database—and executes them by using the various data providers that are part of the NETFramework The SQL Server stored procedure is as follows:
CREATE PROCEDURE ParamOrderProc
@Param1 varchar (10) = ‘Default1’,
@Default varchar (10) = ‘Default2’,
@Param2 varchar (10) = ‘Default3’,
@Param3 varchar (10) = ‘Default4’
ASSELECT @Param1 + ‘, ‘ + @Default + ‘, ‘ + @Param2 + ‘, ‘ + @Param3
Trang 6Using Parameters with SQL Statements and Stored Procedures
This SQL Server stored procedure simplycollects the values of the four parameters youprovide when the stored procedure is
executed, and it returns a character stringthat concatenates their values together
However, the point here is that they are alloptional parameters, which means that notall of them must be specified when youexecute the stored procedure If the code thatexecutes the procedure does not provide avalue for one of the parameters, the defaultvalue specified within the stored procedure isused instead
Unfortunately, however, Access doesn’tsupport optional parameters, so the Access query used in the sample page has only three parameters and no default values:
PARAMETERS Param1 Text(10), Param2 Text(10), Param3 Text(10);
SELECT [Param1] + ‘, ‘ + [Param2] + ‘, ‘ + [Param3] AS Expr1;
The strange effects shown in Figure 10.7 come about because when you call the stored dure, you add the parameters in a different order from which they are defined in the storedprocedures:
proce-oCmd.Parameters.Add(“@Param1”, “Value1”)oCmd.Parameters.Add(“@Param3”, “Value3”)oCmd.Parameters.Add(“@Param2”, “Value2”)
The result shown in Figure 10.7 proves that with the exception of the SqlClientclasses, thenames you provide for parameters have no effect They are ignored, and the parameters are
passed to the stored procedure by position and not by name You get back the three values in
the same order as you specified them, even though the parameters’ names don’t match
However, with the SqlClientclasses, the result is different With these classes, parameters arepassed by name, so you get back the values in an order that matches the order within theParameterscollection The order in which you add them to the Parameterscollection doesn’tmatter; each one will match up with the corresponding named parameter in the stored procedure
Using Default Values in a Stored Procedure
The previous example uses a stored procedure containing optional parameters When you declare
a parameter in a stored procedure in SQL Server and most other enterprise-level databasesystems, you can provide a default value for the parameter In fact, it is required because this ishow the database knows that it is an optional parameter Without a default value, you’ll get anerror if you call the procedure without providing a value for that parameter
Installing the Stored Procedure for This Example
A SQL script named ParamOrderProc.sqlisprovided in the databasessubfolder of thesamples you can download for this book (seewww.daveandal.net/books/6744/) You canuse this script to create the stored procedurefor the example For SQL Server, you openQuery Analyzer from the SQL Server section ofyour Start menu, select the Northwind data-base, and then open the script file and execute
it You must have owner or administratorpermission to create the stored procedure
Trang 7By taking advantage of sensible defaults for your parameters, you can simplify the data accesscode you have to write in your ASP.NET pages and data access components Listing 10.2 showsthe stored procedure used in the sample page for this section of the chapter It is designed toupdate rows in the Orderstable of the Northwind sample database, and you can see that it takes
12 parameters
LISTING 10.2 A Stored Procedure That Provides Sensible Default ValuesCREATE PROCEDURE ParamDefaultProc
@OrderID int, @CustomerID nchar(5),
@OrderDate datetime = NULL, @RequiredDate datetime = NULL,
@ShippedDate datetime = NULL, @ShipVia int = 1,
@Freight money = 25, @ShipName nvarchar(40) = NULL,
@ShipAddress nvarchar(60) = NULL, @ShipCity nvarchar(15) = NULL,
@ShipPostalCode nvarchar(10) = NULL, @ShipCountry nvarchar(15) = NULLAS
IF @OrderDate IS NULLBEGIN
SET @OrderDate = GETDATE()END
IF @RequiredDate IS NULLBEGIN
RAISERROR(‘Procedure ParamDefaultProc: you must
provide a value for the RequiredDate’,
1, 1) WITH LOGRETURN
END
IF @ShipName IS NULLBEGIN
SELECT @ShipName = CompanyName, @ShipAddress = Address,
@ShipCity = City, @ShipPostalCode = PostalCode,
@ShipCountry = CountryFROM Customers
WHERE CustomerID = @CustomerIDEND
Using Optional Parameters in a Stored Procedure
Optional parameters will only work really successfully when you use the SqlClientdata providerbecause none of the other data providers (as discussed earlier in this chapter) pass parameters byname To use other data providers, which pass parameters by position, you would have to make surethat the optional parameters are located at the end of the list and provide values for all the parame-ters up to the ones that you want to use the default values
B E S T P R A C T I C E
Trang 8Using Parameters with SQL Statements and Stored Procedures
UPDATE Orders SETOrderDate = @OrderDate, RequiredDate = @RequiredDate,ShippedDate = @ShippedDate, ShipVia = @ShipVia,
Freight = @Freight, ShipName = @ShipName,ShipAddress = @ShipAddress, ShipCity = @ShipCity,ShipPostalCode = @ShipPostalCode, ShipCountry = @ShipCountryWHERE
OrderID = @OrderID
The first 2 parameters, the order ID and the customer ID, are required They are used to selectthe correct rows in the Ordersand Customerstables within the stored procedure However, theremaining 10 parameters are all optional Notice that a couple of them are set to sensible defaultvalues (the freight cost and shipper ID), but the remainder are set to NULLby default
Inside the stored procedure, the code can figure out what to do if the user doesn’t providevalues for some of the parameters For example, if the order date is not specified, the obviousvalue to use is the current date, which is provided by the GETDATEfunction in SQL Server All youhave to do is test for the parameter being NULL(IF @OrderDate IS NULL)
Writing to the Event Log from SQL Server
If the user doesn’t provide a value for the RequiredDateparameter when he or she executes thestored procedure, you want to prevent the update and flag this as an invalid operation You can
do this by calling the RAISERRORmethod in SQL Server and providing the error message that will
be returned to the user By adding the WITH LOGsuffix, you force SQL Server to write a message toits own error log file and into the Application section of Windows Event Log as well
The values used for the RAISERRORmethod are the message to write to the error and event logs,the severity level (which should be between 0 and 18 for non-system-critical messages), and anarbitrary state value that must be between 1 and 127 It’s also possible to use the RAISERRORmethod to raise system-defined messages or custom messages stored in the SQL Server sysmessagestable SQL Server’s Books Online contains more details
After executing the RAISERRORmethod, the sample page’s code simply returns from the storedprocedure without updating the database row
Providing a Default Shipping AddressThe sample database contains details of the existing customers in the Customerstable, so itwould seem sensible that when a new order is added, the customer’s address details are used by
default In this case, you’re updating order rows rather than adding them, but the code still
demonstrates a technique you could use when inserting rows
If the user does not provide a value for the @ShipNameparameter (the name of the order ent), the stored procedure collects the values for all the address columns from the Customertable,using the CustomerIDvalue provided in the mandatory second parameter to the stored proce-dure
recipi-LISTING 10.2 Continued
Trang 9Then, finally, the stored procedure executes a SQL statement with a combination of the valuesthat were specified for the parameters, specified as defaults, or calculated within the storedprocedure code.
The sample page shown in Figure 10.8 uses this stored procedure It contains a series of controlswhere you can enter the values for the parameters and specify whether they are to be set If acheck box is not set, that parameter will not be added to the Parameterscollection of the Commandinstance, so the default parameter value will be used within the stored procedure
FIGURE 10.8The sample page that uses the storedprocedure with optional parameters
The right-hand column of the page shows thevalues currently in the row in the database(for columns that can be edited) When youfirst load the page, this column is empty.You’ll see that it is populated after you executethe stored procedure, so you can tell whateffects your settings have had on the row
The Code for the Stored Procedure Default Values Sample Page
The code used for the sample page contains
an event handler routine named ExecuteSprocthat runs when the Execute button is clicked Listing 10.3 shows the relevant sections of thiscode After you create the Connectionand Commandinstances and specify that you’re working with
a stored procedure, you add the two mandatory parameters (the values for which are specified
Trang 10Using Parameters with SQL Statements and Stored Procedures
Then you test each check box to see if it’s set If it is set, you add a parameter to the Commandinstance with the value collected from the appropriate text box or drop-down list After you’veadded all the parameters, you execute the stored procedure and then check whether any rowswere updated If no rows were updated, you display an error message in the page
LISTING 10.3 The ExecuteSprocRoutine That Executes the Stored ProcedureSub ExecuteSproc(sender As Object, args As EventArgs)
‘ get connection string, create connection and commandDim sConnect As String = ConfigurationSettings.AppSettings( _
“NorthwindSqlClientConnectString”)Dim oCon As New SqlConnection(sConnect)
Dim oCmd As New SqlCommand(“ParamDefaultProc”, oCon)Dim iRows As Integer
Try
‘ specify query type, add parameters and execute queryoCmd.Parameters.Add(“@OrderID”, iOrderID)
oCmd.CommandType = CommandType.StoredProcedureoCmd.Parameters.Add(“@CustomerID”, sCustomerID)
If chkOrderDate.Checked ThenoCmd.Parameters.Add(“@OrderDate”, _
DateTime.Parse(txtOrderDate.Text))End If
If chkRequiredDate.Checked ThenoCmd.Parameters.Add(“@RequiredDate”, _
DateTime.Parse(txtRequiredDate.Text))End If
If chkShippedDate.Checked ThenoCmd.Parameters.Add(“@ShippedDate”, _
DateTime.Parse(txtShippedDate.Text))End If
If chkShipVia.Checked ThenoCmd.Parameters.Add(“@ShipVia”, _
Integer.Parse(lstShipVia.SelectedValue))End If
If chkFreight.Checked ThenoCmd.Parameters.Add(“@Freight”, _
Decimal.Parse(txtFreight.Text))End If
If chkShipName.Checked ThenoCmd.Parameters.Add(“@ShipName”, txtShipName.Text)End If
If chkShipAddress.Checked Then
Trang 11oCmd.Parameters.Add(“@ShipAddress”, txtShipAddress.Text)End If
If chkShipCity.Checked ThenoCmd.Parameters.Add(“@ShipCity”, txtShipCity.Text)End If
If chkShipPostalCode.Checked ThenoCmd.Parameters.Add(“@ShipPostalCode”, txtShipPostalCode.Text)End If
If chkShipCountry.Checked ThenoCmd.Parameters.Add(“@ShipCountry”, txtShipCountry.Text)End If
‘ execute procedure and see how many rows were affectedoCon.Open()
iRows = oCmd.ExecuteNonQuery()
‘close connection afterwardsoCon.Close()
‘ display confirmation or error message If RequiredDate value
‘ not specified the error will be recorded in Windows Event Log
If iRows > 0 ThenlblResult.Text = “Updated “ & iRows.ToString() & “ row(s).”
ElselblResult.Text = “<font color=’red’><b>ERROR: </b> No “ _
& “rows were updated - see the “ _
& “Application Log in Event Viewer</font>”
End If
Catch oErr As Exception
‘ be sure to close connection if error occurs
‘ can call Close more than once if required - no exception
‘ is generated if Connection is already closedoCon.Close()
lblResult.Text = “<b>ERROR: </b>” & oErr.Message
End Try
‘ now collect values from table and display them in the page
‘ code not shown here
End SubLISTING 10.3 Continued
Trang 12Using Parameters with SQL Statements and Stored Procedures
Experimenting with the Stored Procedure Default Values Sample Page
To check that the sample page’s code works as expected, you can try entering values for thevarious columns in the row and setting the check boxes to force a parameter to be supplied forthat column For example, if you set the order date, shipper ID, and address details check boxes,you’ll see that these columns are updated within the row (see Figure 10.9)
FIGURE 10.9Updating the order date, shipper, andaddress columns
However, if you then clear the check box for the customer name (@ShipName) and execute thestored procedure again, you’ll see that the values in the Customerstable for this customer arecollected and used to update the row (see Figure 10.10)
FIGURE 10.10Using the default address details if theyare not specified as parameters
Finally, you can try clearing the check box for the @RequiredDateparameter and executing thestored procedure again You’ll see an error message displayed at the foot of the page If youselect Start, Programs, Administrative Tools; open Event Viewer; and look in the Application Logsection, you’ll see the entry that the stored procedure creates (see Figure 10.11)
Trang 13Filling a DataSet Instance With and Without a Schema
ADO.NET developers take for granted the ease with which they can fill a DataSetinstance from adatabase To do this, you simply create an empty DataSetinstance, create a Connectioninstance,and specify a SQL statement or stored procedure Then you create a DataAdapterinstance fromthese and call its Fillmethod to pull the data from the database and push it into the data set.However, when you think about it, there’s a lot going on here The internal DataSetcode has tofigure out the schema of the database table(s) and build this structure And what happens if thetable has a primary key defined or if there are relationships between the tables in the database?What about if there are NULLvalues in some rows or orphan rows in a child table?
The same questions apply when you fill a DataSetinstance from an XML document Where doesthe primary key come from, if there is one? And because XML documents are often hierarchical
in nature, how does the internal DataSetcode know what tables and columns to create, andwhat does it do when values are missing for some of the columns?
In response to most of the concerns described in the preceding section, many developers load aschema first, before they attempt to load either relational data (via a DataAdapterinstance) or anXML document The schema causes the DataSetinstance to create the required tables(s), withcolumns that are of the required data type, size, and precision The schema can also force theinternal DataSetcode to create the primary keys and foreign keys for the tables, establishing theDataRelationobjects that reference the relationships between the tables
FIGURE 10.11The message written to the event logwhen no RequiredDatevalue isprovided
Trang 14Filling a DataSetInstance With and Without a Schema
What is the most efficient way to do this? The internal DataSetcode seems to cope perfectly wellwithout a schema in most cases; the only common exception is irregularly structured XMLdocuments The following sections look at an example that gives you a chance to compare theperformance on your system
The DataAdapter.MissingSchemaActionProperty
Do you usually specify a value for theMissingSchemaActionproperty of theDataAdapterinstance when you fill a data set?
If you create the structure from a schema,what happens if the data you load subse-quently doesn’t match the schema? Forexample, there may be extra columns in thetables that are returned by the SQL statement
or stored procedure, or there may be extranested elements in an XML document thatyou use to load your DataSetinstance
By default, the internal DataSetcode willautomatically add to its tables any extracolumns it requires, and it populates thesefrom the data that is used to fill or load theDataSetinstance (regardless of whether youuse the Fillmethod for relational data or load an XML document) However, you can controlthis process yourself by setting the MissingSchemaActionproperty of the DataAdapterinstance toone of the values shown in Table 10.1
TA B L E 1 0 1The Values from the MissingSchemaActionEnumerationValue Description
Add This is the default Tables and columns that occur in the source data are added to the DataSet
instance Only the data type of the column is set automatically Other metadata, such as theprimary key, column size, and precision, is not set
AddWithKey Tables and columns that occur in the source data are added to the DataSetinstance All
meta-data about the columns is loaded, including the primary key, column size, and precision
Ignore Any tables or columns not already in the DataSetinstance are ignored and are not added Using
this value is a good way to prevent the contents of the DataSetinstance from varying from apredefined structure
Error An exception is raised if a table or column is found in the source data that does not already exist
in this DataSetinstance Using this value is a good way to detect when the source data variesfrom the predefined structure
The Sample Page for Filling a DataSet InstanceYou can use the sample page discussed in this section in several ways It contains a functionnamed FillDataSetthat generates a DataSetinstance containing three related tables This is
Filling a Data Set when the Data Contains Extra Column Elements
It’s possible for an irregularly structured XMLdocument to have extra nested elements that
do not match the schema you use In thiscase, the default behavior of the DataSetinstance is to add any columns (and tables)required to load all the data—just as if therewere extra columns in relational data However,it’s also possible that an XML document hasnested elements missing (that is, omitted) sothat there is no data available to fill some ofthe columns in some of the rows in a table inthe DataSetinstance In that case, the values
in these columns are all set to NULL
Trang 15much the same code as is used several times in Chapter 4 The data is extracted from theCustomers, Orders, and Order Detailstables in the Northwind database, and the code adds tworelationships, named CustOrdersand OrdersODetails, to the DataSetinstance (see Figure 10.12).
DataSet
Customers
Orders
DataRelation CustOrders
OrderDetails
DataRelation OrdersODetails
FIGURE 10.12The structure of theDataSetinstance for
an example of filling adata set
Your major aim for the routine that generates the DataSetinstance in this example is that youwant to be able to compare the performance and results when you load a schema first and whenyou do not You also want to be able to compare the results when you use different values forthe MissingSchemaActionproperty of the DataAdapterinstance After you create a new emptyDataSetinstance, you test the value of a parameter named bLoadSchema If this value is True, youload a schema from disk into the DataSetinstance:
If bLoadSchema = True ThenDim sSchemaFile As String _
= Request.MapPath(Request.ApplicationPath) _
& “\dataaccess\datasetschema.xsd”
oDataSet.ReadXmlSchema(sSchemaFile)End If
After you create the Connection, you can create the DataAdapterinstance and set theMissingSchemaActionproperty The value of this property is taken from a drop-down list controlnamed lstMissingSchemain the page:
Dim oDA As New OleDbDataAdapter(sCustSQL, oConnect)oDA.MissingSchemaAction = lstMissingSchema.SelectedValue
Then you can fill that DataSetinstance with the three tables you want Afterward, you add thetwo relationships named CustOrdersand OrdersODetailsto the DataSetinstance:
If bLoadSchema = False Then
‘ create relations between the tables
‘ as in previous examples
End If
Trang 16Filling a DataSetInstance With and Without a Schema
However, you do this only if you didn’t load a schema first because the schema declares, andwill have created, the relationships
Viewing the SchemaThe sample page provided with the samples for this book contains a routine named ShowSchema.This routine uses the FillDataSetfunction to create and populate a DataSetinstance and thendisplays the schema in the page so that you can see the result The FillDataSetfunction is calledwith the bLoadSchemaparameter set to Falseso that the internal schema generated within theDataSetinstance is based on the data it loads and the current setting of the MissingSchemaActionproperty
Listing 10.4 shows the ShowSchemaroutine You can see the routine displays the schema only ifthe MissingSchemaActionproperty (as specified in the drop-down list named lstMissingSchema) hasthe value 1(Add) or 4(AddWithKey) If you use any other value for the MissingSchemaActionprop-erty and don’t load a schema first, you won’t get any tables generated in the DataSetinstance
(Look back at Table 10.1 if you’re not sure why this should be the case.)The routine named CreateSQLStatementsthat is called in Listing 10.4 simply creates the SQLstatements that the FillDataSetfunction uses; the CreateSQLStatementsroutine isn’t shown inListing 10.4 After the routine fills the DataSetinstance, the GetXmlSchemamethod is called to getthe schema as a Stringvalue, and the code HTML encodes it and inserts it into a Labelcontrol
Dim oDS As DataSet = FillDataSet(False)lblSchema.Text = “<pre>” _
& Server.HtmlEncode(oDS.GetXmlSchema()) & “</pre>”
ElselblSchema.Text = “Cannot create schema dynamically “ _
& “for Ignore or Error values”
End If
End Sub
Figure 10.13 shows the sample page in action Clicking the View Schema button calls theShowSchemaroutine and shows the result in the page
Trang 17The Schema for MissingSchemaAction.Add
Listing 10.5 contains two extracts from the schema displayed in Figure 10.13, whenMissingSchemaActionis set to Add The first section shows the definition of the Customerstable inthe DataSetinstance, and it’s obvious that the only information it provides is the column nameand the data type The minOccursattribute indicates that values for all the columns are optional
In other words, they could be NULLin the database table, and the equivalent elements could beomitted from an XML representation of the data
LISTING 10.5 The Schema Generated when MissingSchemaActionIs Set to Add
<xs:element name=”Customers”>
<xs:complexType>
<xs:sequence>
<xs:element name=”CustomerID” type=”xs:string” minOccurs=”0” />
<xs:element name=”CompanyName” type=”xs:string” minOccurs=”0” />
<xs:element name=”City” type=”xs:string” minOccurs=”0” />
<xs:element name=”Country” type=”xs:string” minOccurs=”0” />
Trang 18The xs:keyrefelements can then specify the name of the relationship, a reference to the uniqueconstraint that identifies the parent column, and the path and name of the child column Bear
in mind that these constraints are created by the relationships added to the DataSetinstance andare not implemented by the Fillmethod If you didn’t create the relationships, there would be
no xs:uniqueand xs:keyrefelements In other words, if you don’t create the relationships, allthe columns in the table will be optional and not forced to contain unique values
The Schema for MissingSchemaAction.AddWithKey
Listing 10.6 shows the definition of the Customerstable in the schema when MissingSchemaAction
is set to AddWithKey This time, the declaration of each column contains an xs:restrictionelement that defines the data type and the size of the column (For the string values shownhere, the size of the column is the number of characters.)
LISTING 10.6 The Customers Table Definition Generated when MissingSchemaActionIs Set toAddWithKey
Trang 19an auto-increment or IDENTITYcolumn, of type int(Integer) and specifies it to be read-only:
<xs:element name=”OrderID” msdata:ReadOnly=”true”
msdata:AutoIncrement=”true” type=”xs:int” />
When you use MissingSchemaAction.Add, there is no indication at all of the primary keys for thetables—just the specification of the unique column constraints generated by the relationshipsadded to the DataSetinstance However, with MissingSchemaAction.AddWithKey, the final section ofthe schema specifies the primary keys of the Customersand Orderstables, using the msdata:PrimaryKeyattribute You can see these constraints in Listing 10.7
LISTING 10.6 Continued
Trang 20Filling a DataSetInstance With and Without a Schema
LISTING 10.7 The DataSetInstance Constraints Generated when MissingSchemaActionIs Set to AddWithKey
<xs:unique name=”Constraint1” msdata:PrimaryKey=”true”>
LISTING 10.8 The DoTestRoutine to Compare Performance With and Without a SchemaSub DoTest(sender As Object, e As EventArgs)
‘ declare local variablesDim iCount As Integer = 100Dim iLoop As IntegerDim oDS As DataSetDim dStart As DateTimeDim dDiff1, dDiff2 As TimeSpan
CreateSQLStatements()
‘ load DataSet with schemaTrace.Write(“With Schema”, “Start”)dStart = DateTime.Now
For iLoop = 1 To iCount
Trang 21oDS = FillDataSet(True)Next
dDiff1 = DateTime.Now.Subtract(dStart)Trace.Write(“With Schema”, “End”)lblResult.Text &= “Loaded DataSet with schema “ _
& iCount.ToString() & “ times in “ _
& dDiff1.TotalMilliseconds.ToString() & “ ms.<br />”
‘ load DataSet without schema - can’t do it
‘ when MissingSchemaAction is Ignore or Error
If lstMissingSchema.SelectedValue = 1 _
Or lstMissingSchema.SelectedValue = 4 ThenTrace.Write(“Without Schema”, “Start”)dStart = DateTime.Now
For iLoop = 1 To iCountoDS = FillDataSet(False)Next
dDiff2 = DateTime.Now.Subtract(dStart)Trace.Write(“Without Schema”, “End”)lblResult.Text &= “Loaded DataSet without schema “ _
& iCount.ToString() & “ times in “ _
& dDiff2.TotalMilliseconds.ToString() & “ ms.<br />”
‘ calculate differenceDim fRatio As Decimal = (dDiff1.TotalMilliseconds _
- dDiff2.TotalMilliseconds) / dDiff2.TotalMillisecondslblResult.Text &= “With schema is “ & Math.Abs(fRatio).ToString(“p”)
If dDiff1.TotalMilliseconds > dDiff2.TotalMilliseconds ThenlblResult.Text &= “ slower.”
ElselblResult.Text &= “ faster.”
LISTING 10.8 Continued
Trang 22Filling a DataSetInstance With and Without a Schema
You then repeat the process, but this time you instruct the FillDataSetroutine to not load aschema first And, of course, you can do this only when the MissingSchemaActionproperty is set
to Addor AddWithKey If it is set to Erroror Ignore, there will be no data in the DataSetinstance if
no schema was loaded first After all this, you calculate the percentage difference in the timestaken and display this in the page
The Results of Comparing Performance With and Without a Schema
Figure 10.14 shows the result of comparing performance with and without a schema on one ofour (rather aging) test servers You can see that when MissingSchemaActionis set to Add, loading aschema turns out to be on average around 25% slower than simply loading the data into anempty DataSetinstance When you set MissingSchemaActionto AddWithKey, loading a schema first
is around 15% slower on average
FIGURE 10.14 The results of comparing
load times with and without
a schema
This isn’t quite what you would expect, cially because the general opinion seems to bethat loading a schema first gives betterperformance Of course, performance willvary wildly, depending on a whole raft offactors such as disk access times for loadingthe schema, memory and resources availabil-ity on the server, and where the database islocated and the network connection speed
espe-However, we repeated the test on two other machines, including one with SCSI rather than IDE disks, and the results were broadly similar
Remember that loading the schema first always sets the primary keys and the size and precision
of the columns, whereas that information is not added to the DataSetinstance whenMissingSchemaActionis set to Addand no schema is loaded It is possible to add the primary keyinformation, set the size and precision of the columns, and add extra columns, if required, afterthe data has been loaded into the DataSetinstance
Let Us Know the Result on Your Servers
You can use the code and techniques shown
in this chapter to repeat the test on your ownsystems, and you might get very differentresults We’d be pleased to hear what youdiscover You can post your comments andresults at our Web site: www.daveandal.net
Trang 23Writing Provider-Independent Data Access Code
A regular inquiry from ADO.NET developers is how you go about writing code that can easily beconverted to use a different data provider For example, you might have built your pages toaccess a range of database types, using the OLE DB provider and the classes from the System.Data.OleDbnamespace However, if you subsequently decide to use SQL Server as your datasource, you can benefit from using the System.Data.SqlClientclasses with the native TDSprovider for SQL Server But this means changing all the references and classnames in your code
To do that, you could do a search-and-replace operation However, you could instead write codethat is provider independent The only downside of this is that it is marginally less efficientbecause you have to use dynamic linking to the classes at runtime, rather than the static linkingapproach that is used when you specify the classname directly
The sample page shown in Figure 10.15 demonstrates the use of provider-independent dataaccess code The drop-down list allows you to select any of the three provider types (SqlClient,OleDb, or Odbc) and then execute a SQL statement that extracts some values from the database
As long as you are running the page on your local server (http://localhost), you’ll see theconnection string displayed as well
Dynamically Instantiating a NET Framework Class
To instantiate classes dynamically at runtime, as you need to do in this example, you can take
advantage of the remoting technology that is built into the NET Framework Remoting allows
you to call the CreateInstancemethod of the static Activatorobject that is exposed by theremoting system You specify as parameters the fully qualified namespace name and the name
of the class you want; the remoting system returns a handle to a wrapped instance of that class
Using a DataReaderObject when You Don’t Need a DataSetObject
It’s easy to get into the habit of using DataSetobjects for all your projects However, remember that
in many cases you don’t actually need all the extra features that this object has compared to theDataReaderobject The times when you absolutely require a DataSetinstance include the following:
n When you want to remote the data to another server or client
n When you need to store multiple tables and perhaps the relationships between them
n When you need to preserve the full metadata for each column, such as primary keys, defaultvalues, and constraints
n When you intend to perform a subsequent update to the source data
n When you are using sorting or paging in an ASP.NET DataGridcontrolFor other tasks, especially simple server-side data binding, the subsequently lower processing andmemory overhead of the DataReaderclass can substantially improve performance
B E S T P R A C T I C E
Trang 24Writing Provider-Independent Data Access Code
When the object handle is returned, you thencall its Unwrapmethod This instantiates theclass and returns a reference to the resultingobject instance To use these methods, youhave to import the System.Runtime.Remotingnamespace into your ASP.NET page, alongwith any namespaces for other classes thatyou use through static binding However, youdon’t have to import the namespaces for theclasses that you instantiate dynamicallythrough the remoting system
This example uses a DataReaderinstance toextract the data rows and populate theDataGridcontrol on the page, so you need tocreate a Connectioninstance and a Commandinstance from the namespace selected in the drop-down list However, exactly the same principle applies if you want to use a DataSetinstance, inwhich case you’d create a DataAdapterinstance from the appropriate namespace Plus, of course,you might need to create Parameterobjects or objects of other types as well
The Code in the Provider-Independent Data Access Sample PageThe code in the sample page is broken into several routines and a page-level variable that holdsthe fully qualified name of the System.Dataassembly This assembly contains the SqlClient,OleDb, and Odbcnamespaces, from where you create Connectionand Commandobjects Listing 10.9shows the Importdirectives, the page-level variable, the Page_Loadevent handler, and theShowDataroutine
FIGURE 10.15 A demonstration page for
provider-independent dataaccess code
Why Remoted Instances Are Wrapped
The instance is wrapped (yes, this is thecorrect technical term) so that it is not instan-tiated automatically Remember that theremoting system is designed to allow objects
to be passed from one application domain toanother (for example, from one application,across the network, to a remote client appli-cation) The reference to the class may have
to pass through intermediate applicationdomains on its way to the client, and thismeans it can avoid being instantiated withinthose domains
Trang 25LISTING 10.9 The Code for the Provider-Independent Data Access Example
‘ display data using SqlClient classes first time
If Not Page.IsPostback ThenShowData(“Sql”)
End If
End Sub
Sub ShowData(sTypePrefix As String)
‘ set values of namespace and class prefixDim sNameSpace As String = sTypePrefix
If sNameSpace = “Sql” ThensNameSpace = “SqlClient”
End If
‘ bind result to DataGrid to display datadgr1.DataSource = GetDataReader(sNameSpace, sTypePrefix)dgr1.DataBind()
End Sub
Notice that the fully qualified name of the assembly contains not only the assembly name (theDLL name without the file extension) but also the version, culture, and public key token values.You can obtain these values from the NET Configuration Wizard that is installed with the NETFramework You simply select Start, Programs, Administrative Tools, Microsoft NET Framework1.1 Configuration and open the Assembly Cache section by clicking the link in the left pane ofthe window All the installed assemblies are listed in the right pane (see Figure 10.16)