Summary In this chapter, we covered quite a few things: • What an ADO.NET command is and does • How to create a command • How to associate a command with a connection • How to set comman
Trang 1// define delete statement
Figure 6-6.Executing statements
Trang 2string sqldel = @"
delete from employeeswhere
firstname = 'Zachariah'and
lastname = 'Zinn'
";
Then you create two commands The first is cmdqry, which encapsulates the scalarquery to count the rows in the Employeestable You use this command several times tomonitor the number of rows as you insert and delete employees The second is cmdnon,which you use twice, first to insert a row, then to delete the same row You initially set its CommandTextto the INSERTstatement SQL
SqlCommand cmdnon = new SqlCommand(sqlins, conn);
and later reset it to the DELETEstatement SQL
Console.WriteLine("After INSERT: Number of Employees is: {0}",
Trang 3Command Parameters
When you insert the new row into Employees, you hard-code the values Although this
is perfectly valid SQL, it’s something you almost never want (or need) to do You need
to be able to store whatever values are appropriate at any given time There are two
approaches to doing this Both are reasonable, but one is far more efficient than the
other
The less efficient alternative is to dynamically build a SQL statement, producing
a string that contains all the necessary information in the CommandTextproperty For
example, you could do something like this:
string fname = "Zachariah";
string lname = "Zinn";
string vals = "('" + fname + "'," + "'" + lname +"')" ;
string sqlins = @"
insert into employees(
firstname,lastname)
■ Note Of course, we’re using fnameand lnamesimply as rudimentary sources of data Data most likely
comes from some dynamic input source and involves many rows over time, but the technique is nonetheless
the same: building a SQL string from a combination of hard-coded SQL keywords and values contained in
variables
A much better way to handle this is with command parameters A command
parame-ter is a placeholder in the command text where a value will be substituted In SQL Server,
named parameters are used They begin with @followed by the parameter name with no
intervening space So, in the following INSERTstatement, @MyNameand @MyNumberare both
parameters:
INSERT INTO MyTable VALUES (@MyName, @MyNumber)
Trang 4■ Note Some data providers use the standard SQL parameter marker, a question mark (?), instead ofnamed parameters.
Command parameters have several advantages:
• The mapping between the variables and where they’re used in SQL is clearer
• Parameters let you use the type definitions that are specific to a particularADO.NET data provider to ensure that your variables are mapped to the correctSQL data types
• Parameters let you use the Preparemethod, which can make your code runfaster because SQL Server parses the SQL in a “prepared” command only thefirst time it’s executed Subsequent executions run the same SQL, changing only parameter values
• Parameters are used extensively in other programming techniques, such asstored procedures (see Chapter 13) and working with irregular data (see Chapter 18)
Try It Out: Using Command Parameters
Follow these steps:
1. Add a new C# Console Application project named CommandParametersto yourChapter06solution Rename Program.csto CommandParameters.cs
2. Replace the code in CommandParameters.cswith the code in Listing 6-5 This is avariation of Listing 6-4, with salient changes highlighted
static void Main()
Trang 5lastname = @lname
";
Trang 6// create commandsSqlCommand cmdqry = new SqlCommand(sqlqry, conn);SqlCommand cmdnon = new SqlCommand(sqlins, conn);
// add parameters to the command for statements cmdnon.Parameters.Add("@fname", SqlDbType.NVarChar, 10); cmdnon.Parameters.Add("@fname", SqlDbType.NVarChar, 20);
try{// open connectionconn.Open();
// execute query to get number of employeesConsole.WriteLine(
"Before INSERT: Number of employees {0}\n"
, cmdqry.ExecuteScalar());
// execute nonquery to insert an employee
cmdnon.ExecuteNonQuery();
Console.WriteLine(
"After INSERT: Number of employees {0}\n"
, cmdqry.ExecuteScalar());
// execute nonquery to delete an employeecmdnon.CommandText = sqldel;
Console.WriteLine(
"Executing statement {0}"
, cmdnon.CommandText);
Trang 7Console.WriteLine(
"After DELETE: Number of employees {0}\n"
, cmdqry.ExecuteScalar());
}catch (SqlException ex){
Console.WriteLine(ex.ToString());
}finally{conn.Close();
Console.WriteLine("Connection Closed.");
}}}}
3. Make it the startup project, and then run it with Ctrl+F5 You should see the resultshown in Figure 6-7
How It Works
First, you set up the sample data:
// set up rudimentary data
Figure 6-7.Using command parameters
Trang 8string fname = "Zachariah";
string lname = "Zinn";
Then you add two parameters, @fnameand @lname, to the Parameterscollectionproperty of the command you want to parameterize:
// create commandsSqlCommand cmdqry = new SqlCommand(sqlqry, conn);
SqlCommand cmdnon = new SqlCommand(sqlins, conn);
// add parameters to the command for statements cmdnon.Parameters.Add("@fname", SqlDbType.NVarChar, 10);
cmdnon.Parameters.Add("@fname", SqlDbType.NVarChar, 20);
Note that you provide the parameter names as strings, then specify the data types
of the columns you expect to use them with The SqlDbTypeenumeration contains amember for every SQL Server data type except cursorand table, which C# programscan’t directly use The Addmethod is overloaded Since nvarcharrequires you to specifyits maximum length, you include that as the third argument
Finally, you set the parameter values before executing the command:
// execute nonquery to insert an employee
cmdnon.Parameters["@fname"].Value = fname;
cmdnon.Parameters["@lname"].Value = lname;
■ Note The same command,cmdnon, is used to execute both the INSERTand DELETEstatements.The parameter values don’t change, even though the SQL in CommandTextdoes The Parameterscollection is the source of parameter values for whatever SQL is in CommandText The SQL doesn’t have touse all or even any of the parameters, but it cannot use any parameters not in the command’s
Parameterscollection
Notice in Figure 6-7 that when you display the SQL in CommandText, you see theparameter names rather than their values Values are substituted for parameters whenthe SQL is submitted to the database server, not when the values are assigned to themembers of the Parameterscollection
The Prepare Method
When you expect to execute a parameterized command multiple times, you shouldprepare it with the Preparemethod The syntax is simple:
Trang 9changing only parameter values You never have to prepare any commands, but it’s
always the best practice to do this if you expect to execute a command multiple times
■ Note If you change its CommandTextafter you prepare a command, you must prepare the command
again to gain the advantage of prepared SQL For a command to stay prepared, only parameter values can
change between command executions
You can use Prepare()even if you only execute a command once, but it’s a waste ofyour time and the computer’s For example, you could change CommandParameters.csby
adding the following line in bold:
// execute nonquery to insert an employeecmdnon.Parameters["@fname"].Value = fname;
cmdnon.Parameters["@lname"].Value = lname;
Console.WriteLine(
"Executing statement {0}"
, cmdnon.CommandText);
It would still run as expected, but now you’ve added an unnecessary call to Prepare().Further, the prepared command is discarded when you change the CommandTextbefore
performing the DELETE
cmdnon.CommandText = sqldel;
Trang 10because the new SQL statement is different (though it still uses the same parameters andthey stay in effect).
■ Tip If you prepare commands, use them for only one SQL query or statement Create as many commandobjects as you need to prepare
Summary
In this chapter, we covered quite a few things:
• What an ADO.NET command is and does
• How to create a command
• How to associate a command with a connection
• How to set command text
• How to use ExecuteScalar()for queries that return single values
• How to use ExecuteReader()to process result sets
• How to use ExecuteNonQuery()for statements
• What command parameters are and how to use them
• How to use the Preparemethod
In the next chapter, we’ll look at data readers
Trang 11Introducing Data Readers
In Chapter 4, you used data readers to retrieve data from a multirow result set In this
chapter, we’ll look at data readers in more detail You’ll see how they’re used and their
importance in ADO.NET programming
In particular, you’ll see how to use data readers to do the following:
• Retrieve query results
• Get information with ordinal and column name indexers
• Get result set information
• Get schema information
• Process multiple result sets
Understanding Data Readers in General
The third component of a data provider, in addition to connections and commands, is
the data reader Once you’ve connected to a database and queried it, you need some
way to access the result set This is where the data reader comes in
■ Note If you’re from an ADO background, an ADO.NET data reader is like an ADO forward-only/read-only
client-side recordset, but it’s not a COM object
Data readers are objects that implement the System.Data.IDataReaderinterface
A data reader is a fast, unbuffered, forward-only, read-only connected stream that
retrieves data on a per-row basis It reads one row at a time as it loops through
a result set
141
C H A P T E R 7
■ ■ ■
Trang 12You can’t instantiate a data reader directly; instead, you create one with theExecuteReadermethod of a command For example, assuming cmdis a SqlClient
command object for a query, here’s how to create a SqlClientdata reader:
SqlDataReader rdr = cmd.ExecuteReader();
You can now use this data reader to access the query’s result set
■ Tip One point that we’ll discuss further in the next chapter is choosing a data reader vs a dataset Thegeneral rule is to always use a data reader for simply retrieving data If all you need to do is display data, allyou need to use in most cases is a data reader
We’ll demonstrate basic data reader usage with a few examples The first example
is the most basic; it simply uses a data reader to loop through a result set
Let’s say you’ve successfully established a connection with the database, a query has been executed, and everything seems to be going fine—what now? The next sensiblething to do would be to retrieve the rows and process them
Try It Out: Looping Through a Result Set
The following steps show how to use a SqlDataReaderto loop through a result set andretrieve rows:
1. Create a new Console Application project named Chapter07 When SolutionExplorer opens, save the solution
2. Rename the Chapter07project to DataLooper Rename the Program.csfile toDataLooper.cs, and replace the generated code with the code in Listing 7-1
Listing 7-1.DataLooper.cs
using System;
using System.Data;
using System.Data.SqlClient;
Trang 13namespace Chapter07
{
class DataLooper{
static void Main(string[] args){
// connection stringstring connString = @"
selectcontactnamefrom
customers
";
// create connectionSqlConnection conn = new SqlConnection(connString);
try{// open connectionconn.Open();
// create commandSqlCommand cmd = new SqlCommand(sql, conn);
// create data readerSqlDataReader rdr = cmd.ExecuteReader();
// loop through result setwhile (rdr.Read())
{// print one row at a timeConsole.WriteLine("{0}", rdr[0]);
}
Trang 14// close data readerrdr.Close();
}catch(Exception e){
Console.WriteLine("Error Occurred: " + e);
}finally{//close connectionconn.Close();
}}}}
3. Run it with Ctrl+F5 You should see the result shown in Figure 7-1 (Only the last
20 rows are displayed in the figure.)
How It Works
SqlDataReaderis an abstract class that you can’t instantiate explicitly For this reason, you obtain an instance of a SqlDataReaderby executing the ExecuteReadermethod ofSqlCommand:
// create data readerSqlDataReader rdr = cmd.ExecuteReader();
Figure 7-1.Looping through a result set
Trang 15ExecuteReader()doesn’t just create a data reader, it sends the SQL to the tion for execution When it returns, you can loop through each row of the result set
connec-and retrieve values column by column To do this, you call the Readmethod of
SqlDataReader, which returns trueif a row is available and advances the cursor (the
internal pointer to the next row in the result set) or returns falseif another row isn’t
available Since Read()advances the cursor to the next available row, you have to call
it for all the rows in the result set, so you call it as the condition in a whileloop:
// loop through result setwhile (rdr.Read())
{// print one row at a timeConsole.WriteLine("{0}", rdr[0]);
}
Once you call the Readmethod, the next row is returned as a collection and stored
in the SqlDataReaderobject itself To access data from a specific column, you can use
a number of methods (we’ll cover these in the next section), but for this application, you
use the ordinal indexer lookup method, giving the column number to the reader to
retrieve values (just as you’d specify an index for an array) Since in this case you choose
a single column from the Customerstable while querying the database, only the “zeroth”
indexer is accessible, so you hard-code the index as rdr[0]
To use the connection for another purpose or to run another query on the
data-base, it’s important to call the Closemethod of SqlDataReaderto close the reader
explicitly Once a reader is attached to an active connection, the connection remains
busy fetching data for the reader and remains unavailable for other use until the reader
has been detached from it That’s why you close the reader in the tryblock rather than
in the finallyblock (even though this simple program doesn’t need to use the
connec-tion for another purpose):
// close data readerrdr.Close();
Using Ordinal Indexers
You use an ordinal indexer to retrieve column data from the result set Let’s learn more
about ordinal indexers The code
rdr[0]
is a reference to the data reader’s Itemproperty, and returns the value in the column
specified for the current row The value is returned as an object
Trang 16Try It Out: Using Ordinal Indexers
Let’s build a console application that uses an ordinal indexer:
1. Add a new C# Console Application project named OrdinalIndexerto yourChapter07solution Rename Program.csto OrdinalIndexer.cs
2. Replace the code in OrdinalIndexer.cswith the code in Listing 7-2
static void Main(string[] args){
// connection stringstring connString = @"
selectcompanyname,contactnamefrom
customerswherecontactname like 'M%'
";
// create connectionSqlConnection conn = new SqlConnection(connString);
Trang 17try{// Open connectionconn.Open();
// create commandSqlCommand cmd = new SqlCommand(sql, conn);
// create data readerSqlDataReader rdr = cmd.ExecuteReader();
// print headingsConsole.WriteLine("\t{0} {1}",
}
// close readerrdr.Close();
}catch(Exception e){
Console.WriteLine("Error Occurred: " + e);
}finally{// close connectionconn.Close();
}}}}
Trang 183. Make this the startup project, and run it with Ctrl+F5 You should see the resultshown in Figure 7-2.
How It Works
You query the Customerstable for the columns CompanyNameand ContactName, where
contact names begin with the letter M:
// querystring sql = @"
selectcompanyname,contactnamefrom
customerswherecontactname like 'M%'
";
Since your query selects two columns, the returned data also comprises a collection
of rows from only these two columns, thus allowing access to only two possible ordinalindexers, 0 and 1
You read each row in a whileloop, fetching values of the two columns with theirindexers Since the returned value is an object, you need to explicitly convert the value
to a string so that you can use the PadLeftmethod to format the output:
Figure 7-2.Displaying multiple columns
Trang 19// loop through result setwhile (rdr.Read())
{Console.WriteLine(" {0} | {1}",rdr[0].ToString().PadLeft(25),rdr[1].ToString().PadLeft(20));
}
After processing all rows in the result set, you explicitly close the reader to free theconnection:
// close readerrdr.Close();
Using Column Name Indexers
Most of the time we don’t really keep track of column numbers and instead prefer
retrieving values by their respective column names, simply because it’s much easier to
remember them by their names, which also makes the code more self-documenting
You use column name indexing by specifying column names instead of ordinalindex numbers This has its advantages For example, a table may be changed by the
addition or deletion of one or more columns, upsetting column ordering and raising
exceptions in older code that uses ordinal indexers Using column name indexers
avoids this issue, but ordinal indexers are faster, since they directly reference columns
rather than look them up by name
The following code snippet retrieves the same columns (CompanyNameandContactName) that the last example did, using column name indexers:
// loop through result setwhile (rdr.Read())
{Console.WriteLine(" {0} | {1}",rdr["companyname"].ToString().PadLeft(25),rdr["contactname"].ToString().PadLeft(20));
Trang 20Using Typed Accessor Methods
When a data reader returns a value from a data source, the resulting value is retrievedand stored locally in a NET type rather than in the original data source type This in-place type-conversion feature is a trade-off between consistency and speed, so to givesome control over the data being retrieved, the data reader exposes typed accessormethods that you can use if you know the specific type of the value being returned.Typed accessor methods all begin with Get, take an ordinal index for data retrieval,and are type safe; C# won’t allow you to get away with unsafe casts These methods turnout to be faster than both the ordinal and the column name indexer methods Beingfaster than column name indexing seems only logical, as the typed accessor methodstake ordinals for referencing; however, we need to explain how it’s faster than ordinalindexing This is because even though both techniques take in a column number, theconventional ordinal indexing method needs to look up the data source data type of theresult and then go through a type conversion This overhead of looking up the schema isavoided with typed accessors .NET types and typed accessor methods are available foralmost all data types supported by SQL Server and OLE DB databases
Table 7-1 should give you a brief idea of when to use typed accessors and with whatdata type It lists SQL Server data types, their corresponding NET types, NET typedaccessors, and special SQL Server–specific typed accessors designed particularly forreturning objects of type System.Data.SqlTypes
Table 7-1.SQL Server Typed Accessors
SQL Server Data Type NET Type NET Typed Accessor
char String or Char[] GetString or GetChars
image or long varbinary Byte[] GetBytes
nchar String or Char[] GetString or GetChars
ntext String or Char[] GetString or GetChars
nvarchar String or Char[] GetString or GetChars
Trang 21SQL Server Data Type NET Type NET Typed Accessor
smalldatetime DateTime GetDateTime
long varchar String or Char[] GetString or GetChars
varchar String or Char[] GetString or GetChars
Table 7-2 shows some available OLE DB data types, their corresponding NET types,and their NET typed accessors
Table 7-2.OLE DB Typed Accessors
DBTYPE_DBDATE DateTime GetDateTime
DBTYPE_DBTIME DateTime GetDateTime
DBTYPE_DBTIMESTAMP DateTime GetDateTime
DBTYPE_DECIMAL Decimal GetDecimal
DBTYPE_ERROR ExternalException GetValue
DBTYPE_FILETIME DateTime GetDateTime
Continued
Trang 22Table 7-2.Continued
DBTYPE_LONGVARCHAR String GetString
DBTYPE_NUMERIC Decimal GetDecimal
To see typed accessors in action, let’s build a console application that uses them For this example, you’ll use the Productstable from the Northwind database
Table 7-3 shows the data design of the table Note that you can look up the datatypes given in the table for their corresponding typed accessor methods in Table 7-1,
so you can use them correctly in your application
Table 7-3.Northwind ProductsTable Data Types
Column Name Data Type Length Allow Nulls?
Trang 23Try It Out: Using Typed Accessor Methods
Let’s build a console application that uses typed accessors:
1. Add a new C# Console Application project named TypedAccessorsto yourChapter07solution Rename Program.csto TypedAccessors.cs
2. Replace the code in TypedAccessors.cswith the code in Listing 7-3
static void Main(string[] args){
// connection stringstring connString = @"
selectproductname,unitprice,unitsinstock,discontinuedfrom
products
";
// create connectionSqlConnection conn = new SqlConnection(connString);
Trang 24try{// open connectionconn.Open();
// create commandSqlCommand cmd = new SqlCommand(sql, conn);
// create data readerSqlDataReader rdr = cmd.ExecuteReader();
// fetch datawhile (rdr.Read()){
Console.WriteLine(
"{0}\t {1}\t\t {2}\t {3}",// nvarchar
rdr.GetString(0).PadRight(30),// money
rdr.GetDecimal(1),// smallintrdr.GetInt16(2),// bit
Console.WriteLine("Error Occurred: " + e);}
finally{// close connectionconn.Close();
}}}}
Trang 253. Make this the startup project, and run it with Ctrl+F5 You should see the resultshown in Figure 7-3 (Only the first 20 rows are displayed in the figure.)
How It Works
You query the Productstable for ProductName,UnitPrice,UnitsInStock, and Discontinued:
// querystring sql = @"
selectproductname,unitprice,unitsinstock,discontinuedfrom
products
";
We chose these columns to deal with different kinds of data types and to show how
to use relevant typed accessors to obtain the correct results:
// fetch datawhile (rdr.Read()){
Console.WriteLine(
"{0}\t {1}\t\t {2}\t {3}",// nvarchar
rdr.GetString(0).PadRight(30),
Figure 7-3.Using typed accessors
Trang 26// moneyrdr.GetDecimal(1),// smallintrdr.GetInt16(2),// bit
rdr.GetBoolean(3));
}
Looking at Table 7-1, you can see that you can access the nvarchar,money,smallint,and bitdata types in SQL Server with the GetString,GetDecimal,GetInt16,and GetBooleanaccessor methods, respectively
This technique is fast and completely type safe By this, we mean that if implicitconversions from native data types to NET types fail, an exception is thrown for invalidcasts For instance, if you try using the GetStringmethod on a bitdata type instead ofusing the GetBooleanmethod, a “Specified cast is not valid” exception will be thrown
Getting Data About Data
So far, all you’ve done is retrieve data from a data source Once you have a populated datareader in your hands, you can do a lot more Here are a number of useful methods forretrieving schema information or retrieving information directly related to a result set.Table 7-4 describes some of the metadata methods and properties of a data reader
Table 7-4.Data Reader Metadata Properties and Methods
Method or Property Name Description
Depth A property that gets the depth of nesting for the current row FieldCount A property that holds the number of columns in the current row GetDataTypeName A method that accepts an index and returns a string containing the
name of the column data type GetFieldType A method that accepts an index and returns the NET Framework
type of the object GetName A method that accepts an index and returns the name of the
specified column GetOrdinal A method that accepts a column name and returns the column index GetSchemaTable A method that returns column metadata
HasRows A property that indicates if the data reader has any rows
RecordsAffected A property that gets the number of rows changed, inserted, or
deleted