|   Register   |  
Search  

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:
  1. 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.


  2. 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.
  3. 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" %>
    
  4. 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;
       }
    }
    
  5. 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();
        }
    }
  6. 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

Rate this:
Recent Comments
There are currently no comments. Be the first to make a comment.