|
|
|
|
Beginning ASP.NET 1.1 Databases: From Novice to Professional
Last Updated 2/3/2009 3:43:02 PM
Abstract
In this chapter from "Beginning ASP.NET 1.1 Databases: From Novice to Professional,"
you'll learn about the DataReader and DataSet objects and an important step in driving
a page with data: how to handle the data correctly after it's been returned from
the database.
'
In Chapters 4 and 5, you saw in detail how how you build and pass commands to a
database, and you discovered the three types of result from a SQL statement:
a confirmation, a scalar value, and a DataReader object. You also saw how to deal
with scalars and confirmations. In this chapter, you'll continue your look at the
third step in driving a page with data: handling the data correctly once it has been
returned from the database. You have a large number of options for this, but they
all boil down to whether you're going to work with the data directly from the database
or save it on the server as disconnected data. You'll look at both options in this
chapter, and you'll use the following techniques, which are all quite commonplace:
- Using the DataReader to work with the results of a query directly from the
database
- Saving query results into a DataSet object on the server away from the
database
- Creating a DataSet locally with your own data completely away from an
external data source
Along the way, you'll look in some detail at both the DataReader and DataSet
objects, their makeup, and their differences. At least one of these two objects will
feature in every data-driven Web page you create, so it's good to be up to speed on
how they work.
This chapter is only the first part of three in your journey through data handling.
For now, we'll assume for the moment that the data you request doesn't
need to be displayed on-screen. In Chapter 7, we'll assume that the data will be
displayed on-screen but is read-only and won't need to be updated. Finally, in
Chapter 8, you'll continue with building pages that allow you to create, modify,
and delete data and reflect those changes back to the data source.
"Why not just look at DataReaders and DataSets as you go along?" we hear
you cry. "Why put this interlude first?" These are good questions, but they have a
simple answer, which we'll quite happily borrow from the world of Perl. For every
data-related task you'll be looking at over the coming chapters, you can follow
this motto: "There's More Than One Way To Do It." But they all stem from how
DataReaders and DataSets work. If you don't look at these objects now and see the
situations in which they're useful, you'll be less likely to choose the right option
when building data-driven pages of your own.
THE DATAREADER OBJECT
The key to the whole topic of data handling is the DataReader object'or, if
you prefer to be data provider'specific, the SqlDataReader,
OleDbDataReader, and
OdbcDataReader objects. True, they're optimized as appropriate
for the technology
they're based on, but their method calls and properties are, for all intensive purposes,
identical, so we can just as easily refer to them all in one go. Rest assured
that if we come to any incongruities, we'll make a note of them before continuing.
The DataReader is a strange little object. You may use it all the time, but it's
intangible, representing only a pipeline in memory between the database and the
Web page waiting for the data. In functional terms, it works much like a phone
connection. While the phone connection is open, the page can speak queries to
the database, which in turn can speak its results back to the page, but once the
connection is closed, there's no trace of it or record of the data returned from
the database except in the page itself. Only if you use another object, such as
the DataSet, can you maintain an in-memory record of the results from the query.
If you like, the additional object is the equivalent of an answering machine or
phone-tapping mechanism.
The upshot of it only being a conduit in memory rather than a permanent
place of storage is that when you access the data in a DataReader, the data is readonly.
It also means you can access the results only one row at a time, and once you
finish with a row and move onto the next one, you can't go back to it. You can go
only forward. Of course, this means there are pros and cons to using only
DataReaders in your page. On the plus side, you have the following:
- Using a DataReader is quick and efficient, as it doesn't need to worry about
keeping track of every bit of data.
- A DataReader doesn't need to store data in memory, so it uses fewer
resources in creating a page.
That's not to say there aren't any disadvantages.
- You can't do anything with the data such as sending changes back to the
database. This would mean referring to data already passed through the
reader, which isn't possible; DataReaders work only from database to client.
- DataReaders require exclusive access to a connection. Once open, nothing
else can use a connection until the DataReader is closed.
A DataReader isn't picky about the amount of data passed through it. You
could request a single item of information from a field or the entire contents of
the database. As long as you understand how to access it, it won't complain.
A DataReader is the resulting object from a call to ExecuteReader() on a
Command object.
SqlDataReader myReader = myCommand.ExecuteReader();
The general practice at this point is to assign (or bind) the values in a
DataReader to controls on the Web form, and indeed that's what you've already
done in the examples in Chapters 3 and 4. You've created a DataGrid control on
the page, bound the data to it, and let ASP.NET take care of the display.
myGrid.DataSource = myReader;
myGrid.DataBind();
So far, all you've seen is the data displayed as a table thanks to that DataGrid,
but you can bind information to several more data-aware Web form controls.
For example, you can use a drop-down list, a set of radio buttons, or a calendar.
We'll spend all of Chapter 7 on data binding, but there's another way to work with
DataReaders that you'll look at here, and that's to iterate through them row by row.
Iterating Through a DataReader
It may seem a waste of time to work through the results of a query row by row and
work with each when you can just bind it to a control and let the control take care
of it all, but consider that data isn't always for display. You may be using a database
table to store user information and site preferences. Rather than displaying it on
the screen, information from these tables may be assigned straightaway to controls'
properties or stored in a business object for use across the whole site. For
example, you may create a Preferences object to store theme information for the
whole site, store values from the database in its properties, and save it as a
Session-level variable. Rather than accessing the database again, you just access the
Session variable. If any preferences are changed during the session, they're saved
to the Session variable and when the session is over, the changes are sent back to
the database. This minimizes both potential connections to the database for this
purpose and also the overhead of using many Session variables at a time. You just
use one with lots of information rather than several containing individual pieces
of information.
To iterate through the contents of a DataReader, you use its Read() method. If
you haven't worked with Reader objects in general before, the idea is simple. A
reader has a pointer that you use to keep track of where you are in the information
coming through your reader. If you like, it's the same kind of thing that happens
when someone uses their finger to keep their place on a page. Until they open the
book and start to read, they can't see anything. The same thing applies in code.
You can't access anything until you call Read() the first time, and each time you call
Read() after that, the DataReader lets another row through for you to use. Read()
will also return a boolean value each time you call it: true if there's another row for
you to work with and false if you've reached the end of the query results, as shown
in Figure 6-1.
Hence, you can use the call to Read() as the condition in a while loop. If your
query returns no results, the first call to Read() ends the loop before you do anything.
If not, the code will keep looping until there are no more results. In short,
your page needs to have this skeleton code in it:
//Create the DataReader by calling ExecuteReader()
SqlDataReader myReader = myCommand.ExecuteReader();
//Iterate through the DataReader in a while loop
while (myReader.Read())
{
.. processing instructions for each row in DataReader
}
//Close DataReader
myReader.Close();
Take care not to call Read() in the while statement
and then again within the
loop'say, in a method call'lest the code skip some of the results. It's easy to do
but hard to track down later in the code.
Besides the actual data processing, it's important you close the DataReader
using Close() after you've finished with it. Once a DataReader has been opened
through a connection, nothing else can use that connection until the DataReader
is closed. Even if an error occurs on that page, the connection is still isolated until
the .NET garbage collector comes to dispose of the open DataReader. This is a
needless waste of resources if all you have to do is make sure you close it. You also
have a maximum number of connections that can be open at any one time, so
under heavy loads, not closing your connections could actually generate errors,
which is definitely not a good thing.
| NOTE: The examples in this chapter use the
SqlClient data provider. You can find
equivalent examples using the OLE DB and ODBC data providers in the code
download for this book.
|
Try It Out: Iterating Through a DataReader
In this example, you'll see that you can do more than just fill a DataGrid with the
results of a database query by passing the results into the grid and calling DataBind().
This is what you've done in every example so far. Instead, you'll write a custom
Publisher class, create an instance of it, and use a row of the Publisher table to
populate it. In real life, you'd probably then use it in the business rules tier of your
Web application, but as this is a straightforward example, you'll define a simple
method on the Publisher object that neatly prints the values of its properties to
the screen. Follow these steps:
- In Web Matrix, create a new ASP.NET page called
Iterating_Through_A_DataReader.aspx and save it to a new folder under
BAND called Chapter06.
- In Design view, drag a label control onto the blank page. You'll use this to
demonstrate that your objects have been created. Make its Text property
empty.
- In All view, make sure the right data provider is included at the top of the
page, like so:
<%@ Page Language="C#" %>
<%@ import Namespace="System.Data" %>
<%@ import Namespace="System.Data.SqlClient" %>
- You have two distinct pieces of code to add to the page. The first is the
class definition of the Publisher object. In Code view, add the following
code:
public class Publisher
{
public string Name;
public string City;
public string Email;
public string Website;
public Publisher()
{}
public string ToString()
{
string description = "";
description = "Name : " + this.Name + "<br />";
description += "City : " + this.City + "<br />";
description += "Contact : <a href=mailto:" + this.Email + ">" + <--
this.Email + "</a><br/>";
description += "Homesite : <a href='" + this.Website + "'>" + <--
this.Website + "</a><br/><br/>";
return description;
}
}
- The second piece of code to add is the code for the Page_Load handler.
void Page_Load(object sender, EventArgs e)
{
//set up connection string and SQL query
string ConnectionString = Convert.ToString( <--
ConfigurationSettings.AppSettings["MSDEConnectString"]);
string CommandText = "SELECT PublisherName, PublisherCity, <--
PublisherContact_Email, PublisherWebsite FROM Publisher";
//create SqlConnection and SqlCommand objects
SqlConnection myConnection = new SqlConnection(ConnectionString);
SqlCommand myCommand = new SqlCommand(CommandText, myConnection);
try
{
//open connection to the database
myConnection.Open();
//run query
SqlDataReader myReader = myCommand.ExecuteReader();
while (myReader.Read())
{
Publisher p = new Publisher();
p.Name = myReader.GetString(0);
p.City = myReader.GetString(1);
p.Email = myReader.GetString(2);
p.Website = myReader.GetString(3);
Label1.Text += p.ToString();
}
myReader.Close();
}
catch (Exception ex)
{
//If an exception occurs, handle it.
throw (ex);
}
finally
{
//Whether you succeed or fail, close the database connection
myConnection.Close();
}
}
- Now save this code, and then run it. When the page loads, you'll see that
the label control contains details of all the publishers in the Publisher
table written out, as in Figure 6-2, but not in tabular form. You have
hyperlinks that work and an easier-to-read collection of data instead.
How It Works
The aim of this page is to demonstrate that you can use a DataReader to source
values for any objects you create, so you start by defining a new object class to use.
In this case, it's a Publisher object whose properties cunningly mirror the information
stored in the Publisher table.
public class Publisher
{
public string Name;
public string City;
public string Email;
public string Website;
public Publisher()
{}
To demonstrate that you've achieved this aim, you can add a label control to
the page and a method that presents the information in a Publisher object neatly
on the screen.
public string ToString()
{
string description = "";
description = "Name : " + this.Name + "<br />";
description += "City : " + this.City + "<br />";
description += "Contact : <a href=mailto:" + this.Email + ">" + <--
this.Email + "</a><br/>";
description += "Homesite : <a href='" + this.Website + "'>" + <--
this.Website + "</a><br/><br/>";
return description;
}
}
All that's left is Page_Load(), and apart from the following section, it's the same
as all the previous examples. The key to this whole page is one small section of the
code, as you saw earlier. Instead of plugging the results of myCommand.ExecuteReader()
right into a DataGrid, you can access the DataReader directly, like so:
//run query
SqlDataReader myReader = myCommand.ExecuteReader();
The following is the while loop where you work through each row in turn. In
this case, create a Publisher object, assign the current row of values from the
Data-Reader to the new object's properties, and then echo them into the label control.
while (myReader.Read())
{
Inside the loop, you just create a new Publisher object and give each of its
properties values from the corresponding fields in the DataReader. You're using
the special accessor methods that are part of the DataReader object here, and you
must select the right one for the type of object you want out of it. All the information
in the Publisher table is strings, so you use GetString(). If you needed integers,
you'd use GetInt32() or GetInt16(). There are 37 different Get----() methods
for the SqlDataReader, and 25 for the OleDbDataReader and OdbcDataReader, so check
the .NET documentation for which one will suit you.
All the Get----() methods take one argument'the index of the field you want
in the current row supplied by the DataReader. So, if you look at the following
query you sent to the database:
string CommandText = "SELECT PublisherName, PublisherCity,
PublisherContact_Email, PublisherWebsite FROM Publisher";
you can see that PublisherName has index 0, PublisherCity has index 1, and so on.
Figure 6-1 illustrates this, too. Why not just use the * wildcard to pull everything
out of the table and then retrieve what you want? Well, you can, but it's just easier
to work out the right index value from the query than from looking up the order of
the fields in the database itself.
| TIP: In addition, it's good practice to query only for the information you require
rather than for everything you may want and then pick and choose.Name the
fields to be pulled from a table, and use a WHERE clause in a SELECT statement
to make sure only the rows required are returned from the database.
|
You can access fields in the current row from the DataReader in any order, and
you don't have to access all the information in a particular row either. Just don't
forget that you can't come back to it later. DataReaders are forward-only but only
at row level.
Publisher p = new Publisher();
p.Name = myReader.GetString(0);
p.City = myReader.GetString(1);
p.Email = myReader.GetString(2);
p.Website = myReader.GetString(3);
As an alternative to using the Get----() methods, you can also access each
field by name. For example:
Publisher p = new Publisher();
p.Name = myReader["PublisherName"].ToString();
p.City = myReader["PublisherCity"].ToString();
p.Email = myReader["PublisherContact_Email"].ToString();
p.Website = myReader["PublisherWebsite"].ToString();
The only problem here is that instead of returning a string, integer, or whatever
you specify using a Get----() method, this approach always returns a generic
System.Object object, which you must cast before you can use. Hence, the call to
ToString() is appended to each line of code.
When you've finished creating the object, you display its details in the label
control. If there's more information in the DataReader, the while loop will start creating
another object. If not, the while loop finishes, and you close the DataReader
by calling Close(), like so:
Label1.Text += p.ToString();
}
myReader.Close();
| NOTE Remember to close a DataReader object using Close() when you've finished
with it. Until you do, you can't use your Connection object for any other queries or
purpose. A DataReader has exclusive access to a Connection until it's closed. This
is true in all data providers.You may want to move myReader.Close() into the
finally clause with myConnection.Close() to make sure.
|
On the same train of thought, DataReaders all rely on their connections to
work, so make sure that the connection isn't closed before the DataReader is finished.
The results won't be pretty.
|
Page:
1,
2,
3 |
next page  |
|
|