Main Points

This is a continuation of our initial work in low level data access layer functions. It's time to start switching over so we can capitalize on that work in the high level data access classes. So, I started by copying all the testing files except for the generics-based DAL class of Marco Bellinaso's which I couldn't get to work. These classes prepare a SqlCommand that's ready to use. I also swapped all the numbers in the files from 04 to 05 and now I'm conducting a quick test identical to the last one on the previous page. This is to make sure that I got everything converted properly and that I'm exactly where I left off last time. Looks good. Here's the call I write into the webpage, followed by the SQL text which it outputs.

<blockquote class="testOutput">
<%= BetaTestingDatabase05PagePathsDataAccess.createACommandLoadedWithSqlForTheTestingDatabase() %>
</blockquote>
SELECT PagePaths.PageID, PagePaths.PageName, WebsitePaths.WebsitePathName, FolderPaths.FolderPathName FROM PagePaths INNER JOIN WebsitePaths ON PagePaths.WebsitePathID = WebsitePaths.WebsitePathID INNER JOIN FolderPaths ON PagePaths.FolderPathID = FolderPaths.FolderPathID

By the time you read this, the testing statements will be removed from the files and everything should be working just fine. However, so you can see the results of live testing, I'm cheating by hard coding the test output into the page so you can follow along step by step. It's time to build on top of the SQL and create the higher level DAL functions that will be specific to the data. This will wind up being four layers of DAL in our n-tier architecture: the base helper methods, the SqlCommands to different databases which live on top of them, the SQL text (like a stored procedure layer) defining what we want the database to do, and at the top of the DAL is this code to hit the database and store the resulting data. I'm not sure if the database hit and the storage should be separated, but we'll see.

Retrieving Data from the Database

A quick note, and this came a surprise to me. I reset the method declaration for the command loaded with SQL after I was finished testing; it now passes the SqlCommand itself and not a string, however, when I accidentally compiled everything to see if the link back to page 4 worked, it didn't through an error. System.Data.SqlClient.SqlCommand It processes just fine, if I only want to confirm the existence of the object I don't even need to convert it to a string first. Interesting.

All right, enough prep work. We're building a DAL, a Data Access Layer; let's freakin' access some data!

using System;
using System.Data;
using System.Data.SqlClient;
using System.Web;
using System.Web.Configuration;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

/// <summary>
/// Obtain the information necessary to create a data packet with everything necessary to construct the link (including absolute URL) for our link factory
/// </summary>
public static class BetaTestingDatabase05PagePaths
{

// Can we generalize this into a helper function so that I can call this helper function and feed it some info?? It seems like I ought to be able to do that. I think this is what Christian Darie and Marco Bellinaso are doing when they're calling custom ExecuteNonQuery methods and stuff. Not sure if I completely understand what they're doing, but surely I can improve this a bit.

// This method queries the Testing Database and stores the results to a data table
// public static DataTable retrieveAllPathInformationFromTheTestingDatabase()
public static string retrieveAllPathInformationFromTheTestingDatabase()
{
.
.
.
}

}

First are all the default using statements (with a couple extra) and the summary for the class. Then I declare this as a class that's public (it can be called and run from anywhere) and static (you don't create instances of it to act on, you just call it). I've also inserted some programming notes to myself for production. Then I declare my first method which retrieves all the path information from the testing database. Remember this method benefits from the work we did on the prior page. All it's going to do is receive a fully SQL-loaded, connection-loaded command object and hit the database with it. I'm still working out the actual implementation but for now I just want to output some data to the page and make sure everything is working.

public static string retrieveAllPathInformationFromTheTestingDatabase()
{

// Get the command for querying the database
SqlCommand commandLoadedWithSqlForTheTestingDatabase = BetaTestingDatabase05PagePathsDataAccess.createACommandLoadedWithSqlForTheTestingDatabase();

// Create a data table to fill with the results from the database
DataTable dataContainerForAllPathInformation = new DataTable();

.
.
.
}

First, I create a SqlCommand, commandLoadedWithSqlForTheTestingDatabase, which calls the static method from the last page. The command object has the connection information for the database and the set of SQL commands that tell the database what we want it to do. In this case, the database is getting information for us and that information will vanish into the aether unless our program has a place to put it. Enter the DataTable. That's all the necessary setup, we can now hit the database.

// Get the database info and store it in the data table
try
{
commandLoadedWithSqlForTheTestingDatabase.Connection.Open();
dataContainerForAllPathInformation.Load(commandLoadedWithSqlForTheTestingDatabase.ExecuteReader());
}
finally
{
commandLoadedWithSqlForTheTestingDatabase.Connection.Close();
}

// Testing
string testVariable = dataContainerForAllPathInformation.Rows.Count.ToString();
return testVariable;

Now we're starting to do things that are a little more dangerous, so I'm using try-catch-finally blocks. While the method is fairly simple, but dangerous doesn't mean complicated, it means doing something that could crash the program. We're now calling an external resource that may not be dependable for us. So all of the potentially dangerous items are wrapped up in the try block. The first thing we do is use our command, commandLoadedWithSqlForTheTestingDatabase, to open a connection to the database. Then I call the DataTable's Load() method to access database information. We specify the command object we're using (since it has the commands we want to send to the database), and call the command's ExecuteReader() method which finally, truly, actually, goes and gets our data... and for an encore, it sticks it in the DataTable so we don't lose it.

If anything bad happens during the try block, it prevents the program from dying horribly. Probably. ;) Just kidding. The catch block then handles the exception. Since there's not anything terribly useful we can do here to handle a database exception, I'm borrowing a common technique, not handling the exception at all to improve performance. It's not uncommon for database errors to be pretty long in coming, so we don't really want to make someone wait even longer while we try to handle an exception. But we do need to tidy up the connection if it still exists, so we place that in a finally block. Whatever else happens, the finally block runs so we can close the connection.

Now we should have a DataTable filled with ooey gooey data-y goodness. When I tried to pass it back the same way I passed the SqlCommand at the top of the page, I got an empty string. So besides making me a little nervous, I changed the method to return a string and altered the testVariable to return the number of rows in the DataTable. 3 Ahhhh... so far so good, we have three rows in our DataTable. Not surprisingly, here's the code in the webpage that pulls the information in.

<span class="testOutput"> <%= BetaTestingDatabase05PagePaths.retrieveAllPathInformationFromTheTestingDatabase() %> </span>

So now for the first time, I can say that we really have a data access layer. It's for testing, so it still needs some extension to plug into the n-tier architecture. However, the next thing I would like to do is see inside the DataTable, and really verify that it's pulling the data we want. Based on what I've learned so far, the quickest way to test this will require taking a side trip into the business logic of the application, so let's pause the DAL for now.

Using an ObjectDataSource

OK, I have to admit, it's not the fastest way to test; if I put a little time in with the properties and methods of the DataTable object, I could probably find a faster alternative. However, I remember a couple weeks ago looking through my programming books and thinking, fantastic, I'm finally gonna try using an ObjectDataSource. I've heard about, it sounds cool, let's do it, right? About half way through the first example I was realizing that using an ObjectDataSource means you actually have to have a DAL object, first. ObjectDataSource isn't like the other data sources, that's why it's so cool, but it means that you have to have the programming done first. At the time, that seemed like too big a jump. But I've got a DAL now. It doesn't do what I want, true, but first, I have to test it before moving on. So why not break out the ObjectDataSource and take it for a spin. :D It's a sufficient excuse for me anyway.

OK, like any programming language, when you're doing things without models in .NET, you can run up some blind alleys before you get where you're going. But when you use it for things that people designed it for and have good examples, .NET is just awesome. This took about three minutes to do; and granted I don't have much memory on my dev box, but it took longer to compile than to program. Here's the code from the webpage.

<asp:ObjectDataSource runat="server" ID="ObjectDataSourceForPagePaths" TypeName="BetaTestingDatabase05PagePaths" SelectMethod="retrieveAllPathInformationFromTheTestingDatabase" >
</asp:ObjectDataSource>

<asp:GridView ID="GridViewToDisplayPagePathsDataTable" runat="server" DataSourceID="ObjectDataSourceForPagePaths">
</asp:GridView>

First, I added an ObjectDataSource and clicked the little arrow. I selected the object that I wanted to use, BetaTestingDatabase05PagePaths; you can see it's been specified as the TypeName attribute of the <asp:ObjectDataSource> tag. Then Visual Studio let's you specify separate methods for SUID operations (SELECT, UPDATE, INSERT, DELETE). Since I just want to verify that everything is working, I specified the SELECT method, and that's it. The method I'm calling, retrieveAllPathInformationFromTheTestingDatabase, is set in the SelectMethod attribute. The ObjectDataSource also takes the standard runat="server" that triggers server side processing, and the ID attribute that identifies it in code.

The GridView creates an HTML table to output the data, and it has the standard runat="server" and ID attributes too. The only other attribute, DataSourceID specifies where it gets its data from; in my case that's the ObjectDataSource, ObjectDataSourceForPagePaths. This is pretty simple. We've specified the class that retrieves the DataTable and the method which does the work in the ObjectDataSource; then we told the GridView to talk to the ObjectDataSource which poured the DataTable into the GridView. And here's our beautiful table with all the data listed out.

PageID PageName WebsitePathName FolderPathName
3 Database04DataAccessLayerDal.aspx http://localhost:1204/webroot/ECBeta/ Testing/AspNet/Databases/
4 CSharp02LinkFactory.aspx http://localhost:1204/webroot/ECBeta/ Testing/AspNet/CSharpProgramming/
5 VolunteerArticleWriters.aspx http://localhost:1204/webroot/EC/ Volunteers/

For an encore, let's take a look at the SQL statement that put it together; we weren't able to do that on the last page.

SELECT PagePaths.PageID, PagePaths.PageName, WebsitePaths.WebsitePathName, FolderPaths.FolderPathName FROM PagePaths
INNER JOIN WebsitePaths ON PagePaths.WebsitePathID = WebsitePaths.WebsitePathID
INNER JOIN FolderPaths ON PagePaths.FolderPathID = FolderPaths.FolderPathID

This is a select operation and the most important part is... yes, the SELECT statement. Note that each field name in the table occurs in the exact order specified in the SELECT statement. Each field name is prefixed with the database table that stores it. This makes the "FROM PagePaths" somewhat redundant, but it's good form, especially since your query may not work without it. Note that the WebsitePathName and FolderPathName are not in the same database table; that's why we need the INNER JOIN statements. In the first statement, the WebsitePaths table is joined to PagePaths (the table in the FROM portion of the statement). The information in each record of the WebsitePaths table checks it's WebsitePathID value and looks for the WebsitePathID value that matches in the PagePaths table - both tables have the WebsitePathID field which is how we program the database to connect the data exactly so that we can perform this operation when we're trying to get the information back out. The second INNER JOIN statement does the same thing for the FolderPaths table. If we didn't create these INNER JOINS, we wouldn't be able to pull data from those tables.

Data Access Output

Rather than wrapping any more testing into the page, let's take a moment to focus on the effect that our ObjectDataSource has. Let's see how it's implemented via HTTP, so we can see what it's really doing. We've covered all the basic functionality of data access, we'll start to customize it on the next page.

The ObjectDataSource has no existence on the page. It's merely a server side construct that performs a function, retrieving and organizing data. Before the page is rendered, it's eliminated from the webpage entirely. However that information is passed into the GridView which renders the data as an HTML table with a wrapper div. The div has a class AspNet-GridView, and a horrific id attribute. The table tag is rendered pretty plainly, and having worked with GridViews before, there are no attributes that can't be overridden with CSS. Interestingly, in the output there is a summary attribute which is an important Section 508 accessibility technique, except I can't find any way to populate the attribute. I did locate another important accessibility element, a Caption attribute which is supposed to supply the caption element... except that it doesn't. I have posted to forums on both issues. The table headings are automatically scoped however, which is excellent, and we've manipulated the data via SQL into a form that reads very linearly. This is a pretty well formed table that just needs a couple things fine tuned. Check out the source...

<div class="AspNet-GridView" id="ctl00_ctl00_placeholderForMainBodyContent_placeholderForSection2_GridViewToDisplayPagePathsDataTable">
<table cellpadding="0" cellspacing="0" summary="">
<thead>
<tr>
<th scope="col">PageID</th>
<th scope="col">PageName</th>
<th scope="col">WebsitePathName</th>

<th scope="col">FolderPathName</th>
</tr>
</thead>
<tbody>
<tr>
<td>3</td>
<td>Database04DataAccessLayerDal.aspx</td>

<td>http://localhost:1204/webroot/ECBeta/</td>
<td>Testing/AspNet/Databases/</td>
</tr>
<tr class="AspNet-GridView-Alternate">
<td>4</td>
<td>CSharp02LinkFactory.aspx</td>
<td>http://localhost:1204/webroot/ECBeta/</td>

<td>Testing/AspNet/CSharpProgramming/</td>
</tr>
<tr>
<td>5</td>
<td>VolunteerArticleWriters.aspx</td>
<td>http://localhost:1204/webroot/EC/</td>
<td>Volunteers/</td>

</tr>
</tbody>
</table>

</div>

The <thead> tag wraps all the <th> tags which list the name of each field. Since we didn't specify a custom name for any of the columns in the SQL, the GridView is using the names from the database. Then the <tbody> tag holds the data for each page that's been returned from the database. This is all the information that was stored in the DataTable, and using this technique we can see all the details to confirm that this matches what we wanted, or to debug if it's not. However, in this case, everything looks golden. For our next phase we'll be trying to customize the data access we've developed to meet our specific needs for the link factory.