Data Access Layer

The purpose of code-behind development is to encapsulate the business logic behind an application within separate, stand-alone software components. The goal is to divide an application into different layers, or tiers—a business logic layer and a user interface layer. Doing so promotes creation of processing components that can be shared among multiple pages, and it becomes easier to maintain the components separately than if coded multiple times on multiple pages.

A second way to encapsulate page components within different layers is to separate "data access" from the user interface. In previous applications this has been accomplished with code-behind files in which data access is part of the processing logic. ASP.NET 2.0 introduces more direct ways to create a data access layer.

In many of the applications you have seen so far, a Web page combines a data source control (such as an AccessDataSource) with an information display control (such as a GridView).

<asp:AccessDataSource id="BookSource" Runat="Server"
  DataFile="../Databases/BooksDB.mdb"
  SelectCommand="SELECT * FROM Books"/>
	
<asp:GridView id="BookGrid" Runat="Server"/>
  DataSourceId="BookSource"/>

Listing 11-10.
Typical use of data source control with information display control.

In this example, a data access method is contained on the page, no different in fact from coding a script on the page to connect to the database, issue an SQL command, and assign the resulting DataReader to the GridView. The only difference is that the script is encapsulated within the AccessDataSource control rather than appearing separately in the script portion of the page.

As you have also seen, this same data access method has been employed on numerous pages throughout these tutorials, each time using the same AccessDataSource to retrieve the same set of database records. Consider what would happen if the design of the database changed. Now, a different set of records may need to be retrieved from the redesigned database, forcing a need to track down and change all instances of the AccessDataSource. Or, consider the same situation arising if the path to the database changes. The point is, the data access method is coded along side the control that displays its returned recordset. A better solution is to uncouple data access from its display. As in the case with code-behind scripts, a separate level, or tier, of data access makes it easier to share data access components and to manage changes wrought by changing database designs and locations.

The <asp:ObjectDataSource> Control

Under ASP.NET 2.0, a new <asp:ObjectDataSource> control permits you to remove the mechanics of data access to a separate software component, and to link to that component through the ObjectDataSource control. The control is, in effect, a "logical" description of data access whose "physical" implementation details are hidden inside a code-behind data-access component. An information display control such as a GridView binds to the ObjectDataSource, which solicits behind-the-scenes data access by the data-access component. A general format for coding an ObjectDataSource is given in Figure 11-9.

<asp:ObjectDataSource id="id" Runat="Server"
  TypeName="class"
  SelectMethod="method"
/>
Figure 11-9. General format for <asp:ObjectDataSource> control.

An id is required for reference by the control that links to the data source. TypeName gives the name of a Visual Basic class containing the data access component; SelectMethod gives the name of the data access method contained in the class, and which returns the recordset bound to the display control.

Creating a Data Access Class

A Visual Basic data access class is a stand-alone component that retrieves and returns a set of records from a database. This work is performed by a data-access method coded as part of the class. In the following listing, a BookInfo class is defined. It contains a Get_Books() method (function) that retrieves a set of records from the Books table of the BooksDB.mdb database and returns the DataReader to any ObjectDataSource that calls it.

Imports System.Data.OleDb

Public Class BookInfo

  Function Get_Books()
    Dim DBConnection As OleDbConnection
    Dim DBCommand As OleDbCommand
    Dim DBReader As OleDbDataReader
    Dim SQLString As String
    DBConnection = New OleDbConnection( _
      "Provider=Microsoft.Jet.OLEDB.4.0;" & _
      "Data Source=c:\eCommerce\Databases\BooksDB.mdb")
    DBConnection.Open()
    SQLString = "SELECT * FROM Books ORDER BY BookID"
    DBCommand = New OleDbCommand(SQLString, DBConnection)
    DBReader = DBCommand.ExecuteReader()
    Return DBReader
    DBReader.Close()
    DBConnection.Close()
  End Function

End Class

Listing 11-11.
A BookInfo data access class.

The app_Code Directory

Like a code-behind file, the Visual Basic class is saved with the extension .vb. In this example, the file is saved as BookInfo.vb. In order to make the class accessible to all pages that call it, it must be saved in the app_Code directory located in the root directory of the application. In the current example, the app_Code directory appears inside the c:\eCommerce root directory.

Binding to a Data Access Class

Once a data access class has been placed inside the app_Code directory, it is called by an ObjectDataSource control for binding its output to a display control coded on the page. In the following example, a GridView appears on a page. The GridView is linked to an ObjectDataSource through its DataSourceID. The ObjectDataSource points to the BookInfo class contained in the BookInfo.vb file through its TypeName attribute, and it calls the class's Get_Books() method through its SelectMethod attribute.

<h3>Data Access with an ObjectDataSource</h3>

<asp:ObjectDataSource id="BookSource" Runat="Server"
  TypeName="BookInfo"
  SelectMethod="Get_Books"/>

<asp:GridView id="GridView" Runat="Server"
  AutoGenerateColumns="False"
  DataSourceID="BookSource">
  
  <Columns>
  
  <asp:BoundField
    HeaderText="Book ID"
    DataField="BookID"/>
  
  <asp:BoundField
    HeaderText="Title"
    DataField="BookTitle"/>
  
  <asp:BoundField
    HeaderText="Price"
    DataField="BookPrice"/>
  
  </Columns>
  
</asp:GridView>

Listing 11-12.
Code to bind an ObjectDataSource to a GridView.

As a result, the Get_Books() method returns the retrieved DataReader (DBReader) to the ObjectDataSource, which binds the recordset to the GridView.

Example

Data Access with an ObjectDataSource

Book IDTitlePrice
DB111Oracle Database69.99
DB222Databases in Depth29.95
DB333Database Processing136.65
DB444Access Database Design34.95
DB555SQL Server 200529.99
HW111How Computers Work29.99
HW222Upgrading and Repairing PCs59.99
HW333USB System Architecture49.99
HW444Designing Embedded Hardware44.95
HW555Contemporary Logic Design102.95
SW111Java How to Program98.59
SW222C Programming Language44.25
SW333Programming C#44.95
SW444Programming PHP39.95
SW555Visual Basic.NET Programming49.99
SY111Operating System Concepts95.75
SY222The UNIX Operating System19.95
SY333Windows Server 200329.99
SY444Linux in a Nutshell44.95
SY555Mastering Active Directory49.99
WB111Ajax in Action22.67
WB222Professional ASP.NET 2.032.99
WB333Cascading Style Sheets39.95
WB444DOM Scripting23.09
WB555Microsoft ASP.NET 2.029.99
Figure 11-10. Binding an ObjectDataSource to a GridView.

You should notice identical coding between this BookInfo.vb data-access class and the code-behind DBPage.vb class used in the previous tutorial. You should also notice that the page using the data-access class does not require inheriting the class nor a src pointer to the location of the class. All links between the Web page and the data-access class are given in the TypeName and SelectMethod attributes of the ObjectDataSource. An ObjectDataSource is simply easier to use than a code-behind file for supplying database information to a server control.

In the current example, the ObjectDataSource returns all records and all fields from the Books table. Display of selected fields from the returned recordset is governed by the BoundFields defined in the GridView. An alternative design for the BookInfo class is to create different methods for different returned recordsets. Then, different pages can call these different methods to return different recordsets.

Passing Parameters to a Data Access Class

An ObjectDataSource can be supplied with control parameters to restrict access to selected records. In the following example, a DropDownList permits the user to select the types of books to display.

Data Access with ObjectDataSource Parameters



Book IDTitlePrice
DB111Oracle Database69.99
DB222Databases in Depth29.95
DB333Database Processing136.65
DB444Access Database Design34.95
DB555SQL Server 200529.99
Figure 11-11. Selecting parameters for an ObjectDataSource.

The DropDownList is itself populated by an ObjectDataSource that calls a new method added to the previous BookInfo class. Coding for the DropDownList and for the new Get_Types() method is shown below.

<asp:ObjectDataSource id="TypeSource" Runat="Server"
  TypeName="BookInfo"
  SelectMethod="Get_Types"/>

<asp:DropDownList id="TypeList" Runat="Server"
  AutoPostBack="True"
  DataSourceID="TypeSource"
  DataTextField="BookType"
  DataValueField="BookType"/>

Listing 11-13.
Code for DropDownList and its ObjectDataSource.
Imports System.Data.OleDb

Public Class BookInfo

  Function Get_Types()
    Dim DBConnection As OleDbConnection
    Dim DBCommand As OleDbCommand
    Dim DBReader As OleDbDataReader
    Dim SQLString As String
    DBConnection = New OleDbConnection( _
      "Provider=Microsoft.Jet.OLEDB.4.0;" & _
      "Data Source=c:\eCommerce\Databases\BooksDB.mdb")
    DBConnection.Open()
    SQLString = "SELECT DISTINCT BookType FROM Books ORDER BY BookType"
    DBCommand = New OleDbCommand(SQLString, DBConnection)
    DBReader = DBCommand.ExecuteReader()
    Return DBReader
    DBReader.Close()
    DBConnection.Close()
  End Function
	
	...
  
End Class

Listing 11-14.
Revised BookInfo class with Get_Types() method added.

Code for the GridView is identical to the previous example. Its ObjectDataSource, though, now includes a ControlParameter identifying the SelectedValue from the DropDownList as a parameter named BookType that is passed to the Get_Books() method of the BookInfo class.

<asp:ObjectDataSource id="BookSource" Runat="Server"
  TypeName="BookInfo"
  SelectMethod="Get_Books">
  <SelectParameters>
    <asp:ControlParameter 
      Name="BookType"
      ControlID="TypeList"
      PropertyName="SelectedValue"/>
  </SelectParameters>
</asp:ObjectDataSource>

<asp:GridView id="BookGrid" Runat="Server"
  AutoGenerateColumns="False"
  DataSourceId="BookSource">
  
  <Columns>
  
  <asp:BoundField
    HeaderText="Book ID"
    DataField="BookID"/>
  
  <asp:BoundField
    HeaderText="Title"
    DataField="BookTitle"/>
  
  <asp:BoundField
    HeaderText="Price"
    DataField="BookPrice"/>
  
  </Columns>
  
</asp:GridView>

Listing 11-15.
Code for GridView and its ObjectDataSource.

The Get_Books() method of the BookInfo class is revised to accept the passed BookType value identified in the ObjectDataSource's ControlParameter. This value is associated with the parameter @BookType in a WHERE clause added to the revised SELECT statement.

Imports System.Data.OleDb

Public Class BookInfo
  
  Function Get_Types()
    Dim DBConnection As OleDbConnection
    Dim DBCommand As OleDbCommand
    Dim DBReader As OleDbDataReader
    Dim SQLString As String
    DBConnection = New OleDbConnection( _
      "Provider=Microsoft.Jet.OLEDB.4.0;" & _
      "Data Source=c:\eCommerce\Databases\BooksDB.mdb")
    DBConnection.Open()
    SQLString = "SELECT DISTINCT BookType FROM Books ORDER BY BookType"
    DBCommand = New OleDbCommand(SQLString, DBConnection)
    DBReader = DBCommand.ExecuteReader()
    Return DBReader
    DBReader.Close()
    DBConnection.Close()
  End Function
  
  Function Get_Books (BookType as String)
    Dim DBConnection As OleDbConnection
    Dim DBCommand As OleDbCommand
    Dim DBReader As OleDbDataReader
    Dim SQLString As String
    DBConnection = New OleDbConnection( _
      "Provider=Microsoft.Jet.OLEDB.4.0;" & _
      "Data Source=c:\eCommerce\Databases\BooksDB.mdb")
    DBConnection.Open()
    SQLString = "SELECT * FROM Books WHERE BookType = @BookType ORDER BY BookID"
    DBCommand = New OleDbCommand(SQLString, DBConnection)
    DBCommand.Parameters.AddWithValue("@BookType", BookType)
    DBReader = DBCommand.ExecuteReader()
    Return DBReader
    DBReader.Close()
    DBConnection.Close()
  End Function
  
End Class

Listing 11-16.
Code for revised Get_Books() method of BookInfo class.

The revised SELECT statement includes a reference to parameter @BookType, a placeholder for a value that restricts selection of records to those with this value in the BookType field. There is a need, then, to associate this parameter with an actual value.

The Command Object's Parameters Collection

A value is associated with a parameter appearing in a Command object (DBCommand in this example) by adding a parameter name and value to the Command object's Parameters collection. A general format for this collections' AddWithValue() method is shown in Figure 11-12.

CommandObject.Parameters.AddWithValue("@parameter", value)
Figure 11-12. General format to add a parameter to a Command object.

The name of the parameter, @parameter, appearing in the Command object is associated with a value. In the current example, this value comes from the BookType argument passed to the Get_Books() method when it is called by the ObjectDataSource. Therefore, the following code appears in the script:

  DBCommand.Parameters.AddWithValue("@BookType", BookType)

The @BookType parameter appearing in the SELECT statement is replaced by the BookType value passed to the Get_Books() method. The selected records are then returned in the DataReader passed back to the ObjectDataSource and bound to the GridView.