This routine processes a SQL statement sqlText on a connected database linkToDB, expecting no returned results.. This routine processes a SQL statement sqlText on a connected database l
Trang 1Along those same generic lines, the SqlDataReader object’s GetSchemaTable method returns
a DataTable instance that describes the structure of the queried data The new table’s content includes columns such as ColumnName, IsKey, and DataTypeName, plus about two dozen
more that you can use to understand the makeup of the incoming data See the Visual Studio online help entry for “SqlDataReader.GetSchemaTable Method” for more information about this method
Processing More Complicated Results
SQL Server supports returning multiple record sets in a single query You can generate them
by sending a batch of two or more semicolon-delimited SELECT statements within a single SqlCommand object’s command text, or by executing a stored procedure that generates
multiple selections
When retrieving multiple record sets, the returned SqlDataReader initially refers to the first set of records To access the second set, call the reader’s NextResult method The method returns False after it passes the final results set Just as with the reader’s view of individual data rows, SqlDataReader cannot return to an earlier results set.
Note The OLE DB and ODBC providers also support nested results, where a single row might contain subordinate data rows The SQL Server provider does not support nested sets.
If you prefer to process the data returned from the query as XML, use the SqlCommand object’s ExecuteXmlReader method (or the asynchronous BeginExecuteXmlReader and EndExecuteXmlReader methods), which returns a System.Xml.XmlReader instance Your query must include the appropriate XML-specific keywords (such as FOR XML), or it must return
valid XML content, such as from a table field
Processing Database Queries: C#
1 Open the “Chapter 9 CSharp” project from the installed samples folder The project
in-cludes a Windows.Forms class named StateBuilder and a sealed class named General.
2 Open the code for the General class This class centralizes much of the database
func-tionality for the sample application Locate the GetConnectionString function, a routine that uses a SqlConnectionStringBuilder to create a valid connection string to the sample
database It currently includes the following statements:
builder.DataSource = @"(local)\SQLExpress";
builder.InitialCatalog = "StepSample";
builder.IntegratedSecurity = true;
Adjust these statements as needed to provide access to your own test database
Trang 23 Locate the ExecuteSQL method This routine processes a SQL statement (sqlText) on a
connected database (linkToDB), expecting no returned results Within the try block, add
the following code:
SqlCommand commandWrapper = new SqlCommand(sqlText, linkToDB);
commandWrapper.ExecuteNonQuery();
4 Locate the ExecuteSQLReturn method This routine processes a SQL statement (sqlText)
on a connected database (linkToDB), collecting a single return value from the database and returning it to the calling code Within the try block, add the following statements:
SqlCommand commandWrapper = new SqlCommand(sqlText, linkToDB);
return commandWrapper.ExecuteScalar();
5 Locate the OpenReader method This function processes a SQL statement (sqlText) on
a connected database (linkToDB), creating a SqlDataReader object to process the re-turned data rows Within the try block, add the following lines:
SqlCommand commandWrapper = new SqlCommand(sqlText, linkToDB);
return commandWrapper.ExecuteReader();
6 Open the source code view for the StateBuilder form Locate the RefreshEverything
routine Just after the “See if a custom state already exists” comment, add the following code:
sqlText = "SELECT * FROM StateRegion WHERE RegionType = 99";
stateReader = General.OpenReader(sqlText, linkToDB);
if ((stateReader != null) && (stateReader.HasRows == true))
{
// - Existing custom state record
stateReader.Read();
ActiveStateID = (long)(int)stateReader["ID"];
AddName.Text = (string)stateReader["FullName"];
AddAbbreviation.Text = (string)stateReader["Abbreviation"];
}
else
{
// - No custom state record
AddName.Clear();
AddAbbreviation.Clear();
}
if (stateReader != null) stateReader.Close();
This code uses the General.OpenReader function from step 5 to obtain a SqlDataReader instance built from a SQL statement (sqlText) and a connection (linkToDB) If the reader
contains at least one row, the code accesses specific fields in that first row to populate various internal and onscreen values
Trang 37 Run the program, a simple database application that lets you create, modify, and re-move a single “state” record On the Add A State tab, enter New C Sharp in the New State Name field and add CS in the New Abbreviation field The SQL statement that will
add the new record to the StateRegion table appears just below the edit fields Click
Add to create the record
8 Use the Rename A State tab to make changes to the test record When you are finished
with the record, use the Delete A State tab to remove the test record
Processing Database Queries: Visual Basic
1 Open the “Chapter 9 VB” project from the installed samples folder The project includes
a Windows.Forms class named StateBuilder and a module named General.
2 Open the code for the General module This file centralizes much of the database
func-tionality for the sample application Locate the GetConnectionString function, a routine that uses a SqlConnectionStringBuilder to create a valid connection string to the sample
database It currently includes the following statements:
builder.DataSource = "(local)\SQLExpress"
builder.InitialCatalog = "StepSample"
builder.IntegratedSecurity = True
Adjust these statements as needed to provide access to your own test database
3 Locate the ExecuteSQL method This routine processes a SQL statement (sqlText) on a
connected database (linkToDB), expecting no returned results Within the Try block, add
the following code:
Dim commandWrapper As New SqlCommand(sqlText, linkToDB)
commandWrapper.ExecuteNonQuery()
Trang 44 Locate the ExecuteSQLReturn method This routine processes a SQL statement (sqlText)
on a connected database (linkToDB), collecting a single return value from the database and returning it to the calling code Within the Try block, add the following statements:
Dim commandWrapper As New SqlCommand(sqlText, linkToDB)
Return commandWrapper.ExecuteScalar()
5 Locate the OpenReader method This function processes a SQL statement (sqlText) on
a connected database (linkToDB), creating a SqlDataReader object to process the re-turned data rows Within the Try block, add the following lines:
Dim commandWrapper As New SqlCommand(sqlText, linkToDB)
Return commandWrapper.ExecuteReader()
6 Open the source code view for the StateBuilder form Locate the RefreshEverything
routine Just after the “See if a custom state already exists” comment, add the following code:
sqlText = "SELECT * FROM StateRegion WHERE RegionType = 99"
stateReader = OpenReader(sqlText, linkToDB)
If (stateReader IsNot Nothing) AndAlso (stateReader.HasRows = True) Then
' - Existing custom state record
stateReader.Read()
ActiveStateID = CLng(stateReader!ID)
AddName.Text = CStr(stateReader!FullName)
AddAbbreviation.Text = CStr(stateReader!Abbreviation)
Else
' - No custom state record
AddName.Clear()
AddAbbreviation.Clear()
End If
If (stateReader IsNot Nothing) Then stateReader.Close()
This code uses the OpenReader function from step 5 to obtain a SqlDataReader instance built from a SQL statement (sqlText) and a connection (linkToDB) If the reader contains
at least one row, the code accesses specific fields in that first row to populate various internal and onscreen values
7 Run the program, a simple database application that lets you create, modify, and re-move a single “state” record On the Add A State tab, enter North Visual Basic in the New State Name field and add VB in the New Abbreviation field The SQL statement
that will add the new record to the StateRegion table appears just below the edit fields
Click Add to create the record
Trang 58 Use the Rename A State tab to make changes to the test record When you are finished
with the record, use the Delete A State tab to remove the test record
Summary
This chapter introduced methods for issuing commands to an ADO.NET connected database, and using those commands to retrieve individual or tabular results The core of this
function-ality is the SqlClient.SqlCommand class, a wrapper for SQL Server queries It includes a variety
of methods that process the contained query, optionally returning either a single value or a set of data rows
The SqlDataReader class provides the row-scanning functionality for results retrieved as a data reader Use the reader’s various Get methods or the default Item property to retrieve field values on each scanned row When finished with a SqlDataReader, always call its Close or Dispose method.
Trang 6Run a SQL query over an ADO.NET connection Create a SqlCommand instance.
Set its CommandText property to the SQL statement Set its Connection property to a valid SqlConnection
instance.
Call the command object’s ExecuteNonQuery method.
Call a SQL Server stored procedure that returns a
single static result
Create a SqlCommand instance.
Set its CommandText property to the stored procedure
name, followed by space-delimited arguments if needed.
Set its Connection property to a valid SqlConnection
instance.
Call the command object’s ExecuteScalar method,
capturing the return value.
Retrieve two sets of data rows from a SQL Server
batch query
Create a SqlCommand instance.
Set its CommandText property to the semicolon-delimited
SQL statements.
Set its Connection property to a valid SqlConnection
instance.
Call the command object’s ExecuteReader method, assigning the return value to a SqlDataReader variable Use the reader’s Read method to access rows in the
batch’s first set of rows.
Call the reader’s NextResult method to access additional
results sets.
Trang 8153
Chapter 10
Adding Standards to Queries
After completing this chapter, you will be able to:
■
■ Understand why parameters are important in queries
■
■ Add parameters to standard selection and data update queries
■
■ Call stored procedures that include both in and out parameters
In ADO.NET, queries pass to external data sources as strings These strings include not only essential command keywords and syntactical elements but also the data values used to limit and fulfill each query Building command strings is an art long practiced by developers in many programming languages, but it’s quite different from NET’s promise of strongly typed data management Why store values as distinct data types at all if you are eventually going
to convert everything to ASCII text?
To push aside these and other deficiencies that stem from inserting all types of data values
into SQL statements, ADO.NET includes the parameter, an object that bridges the gap
be-tween the text-based needs of the external data source’s command processing system and the intelligent data type system that epitomizes NET development This chapter demon-strates query parameters and their uses in SQL Server database queries
Note This chapter focuses on parameters as implemented in the SQL Server provider Although the OLE DB and ODBC providers also implement parameters, there are some minor differences that will be pointed out within the chapter.
The exercises in this chapter all use the same sample project, a tool that uses parameters to re-trieve and update database values Although you can run the application after each exercise, the expected results for the full application might not appear until you complete all exercises in the chapter.
Developing Parameterized Queries
In the SQL Server provider, parameters appear as the System.Data.SqlClient.SqlParameter class By creating relevant parameters and attaching them to SqlCommand instances,
ordi-nary text queries become parameterized queries
Trang 9Note In the OLE DB provider, the parameter class appears as System.Data.OleDb.
OleDbParameter The ODBC equivalent is System.Data.Odbc.OdbcParameter Both of these
classes and the SqlParameter class in the SQL Server provider derive from System.Data.Common DbParameter.
Understanding the Need for Parameters
As mentioned in the “Connection String Builders” section on page 124 of Chapter 8,
“Establishing External Connections,” there are certain risks involved in building SQL state-ments and related string elestate-ments A key risk is the SQL injection attack, in which a user can inadvertently or deliberately alter the intent of a SQL statement by supplying corrupted
content Consider the following statement, which modifies the Employee.Salary value for a
specific employee record:
UPDATE Employee SET Salary = XXX WHERE ID = 5;
It works well if the user provides 50000 or a similar number as the value of XXX But what if
resourceful employee John Doe replaces XXX with the following SQL fragments?
150000 WHERE FirstName = 'John' AND LastName = 'Doe';
UPDATE Employee SET Salary = 50000
The user-supplied content includes a semicolon, effectively turning one statement into a batch of two statements Most programmers design their code to avoid such scenarios, but this type of situation still manages to show up from time to time Parameters help reduce such issues by using typed substitution placeholders instead of unchecked plain-text gaps
in SQL strings Parameters understand how to properly format their replacement values so that SQL injection attacks and other mishaps don’t occur
Parameters solve these problems by making changes to both the SQL statement and the data destined for that statement Instead of piecing together workable SQL statements from
a combination of programmer and user-supplied parts, parameterized query statements ex-ist in a standardized form, free of unknown and unsafe user data Portions of the statement
that require user input exist as named placeholders, @name elements that get replaced
with the final type-specific data values after they have been transmitted to the database
Trang 10This process provides for a more generic command text, and a logical separation between the command and its data
Removing ever-changing data values from SQL statements also increases performance within SQL Server Like many advanced relational database systems, SQL Server compiles each state-ment into an internal format, one that doesn’t require it to constantly parse a text string
to determine its actions If SQL Server encounters the same SQL statement twice, it doesn’t need to go through the time-consuming compilation process again For example, the follow-ing three SQL statements are different in the compiler’s view:
UPDATE Employee SET Salary = 50000 WHERE ID = 5;
UPDATE Employee SET Salary = 56000 WHERE ID = 12;
UPDATE Employee SET Salary = 52000 WHERE ID = 8;
Parameterized queries replace these three instance-specific versions with a generic version of the statement, free of the varying data portions Removing dynamic data values from what would otherwise be standard SQL command structures allows applications to send a much more limited number of queries to SQL Server, queries that show up again and again, and that don’t need to be recompiled every time
Implementing Standard Queries
The UPDATE statement shown previously modifies the salary for an employee record based
on that record’s primary key
UPDATE Employee SET Salary = 50000 WHERE ID = 25;
To prepare the statement for parameters, all elements destined for substitution by the parameter values get replaced with “@” identifiers
UPDATE Employee SET Salary = @NewSalary WHERE ID = @EmployeeID;
In standard SQL statements (all statements other than stored procedures), the names you provide are up to you, so being descriptive is best Each placeholder must begin with the @ sign followed by a unique name Parameter names are not case-sensitive