ASPAlliance.com: The #1 ASP.NET Community
The ASPSmith
Search
D: | Domains | Authors.aspalliance.com | Stevesmith | Articles | Data Access With ADO.NET
Data Access With ADO.NET

By Steven Smith

Writing a data access layer (DAL) for .NET using C# can be a tedious process. Following the best practices laid out in the IBuySpy.com sample site means creating a lot of method calls to wrap stored procedure calls. This is not all that different from how one would accomplish the same task using Visual Basic 6 and COM, but the tediousness is somewhat increased by the apparent increase in the number of lines of code required to perform the task. Scott Guthrie has written a wizard tool that helps a bit with this process (not sure if it has been updated from Beta 1 yet), which you can try here. However, even then you end up with a LOT of code for this relatively simple task. For example, for this call to return a collection of articles which only uses one stored procedure parameter, it requires 3 lines of code for this one parameter. That's 1/3 of the whole function, and this only gets worse for functions that require multiple parameters (as most do).

listArticlesByPopularity method:
public SqlDataReader listArticlesByPopularity(int howmanyrows)
        {

            // Create Instance of Connection and Command Object
            SqlConnection conn = new SqlConnection(DAL.ConnectionString);
            SqlCommand cmd = new SqlCommand("sp_ListArticles", conn);
            cmd.CommandType = CommandType.StoredProcedure;

            SqlParameter pHowManyRows = new SqlParameter("@howmany", SqlDbType.Int,4);
            pHowManyRows.Value = howmanyrows;
            cmd.Parameters.Add(pHowManyRows);

            // Execute the command
            conn.Open();
            SqlDataReader result = cmd.ExecuteReader(CommandBehavior.CloseConnection);

            // Return the datareader result
            return result;
        }

With ADO, creating parameters was a one line process which, while still tedious, was much easier to maintain and read. Spending three lines per parameter may not seem like a big deal, but it gets old really fast, let me assure you (especially when you have to convert over a hundred sp's into DAL methods). Unfortunately, ADO.NET does not provide an overloaded paramter constructor that will create a full-fledged sqlparameter for you. So I created one and stuck it in my DAL base class, from which all of my DAL classes inherit. The function looks like this, and can easily be overloaded to eliminate the need to send it all of the parameters listed here.

pMaker method:
      protected static SqlParameter pMaker(String parameterName, SqlDbType DbType, Int32 Size,
ParameterDirection Direction, Object Value)
        {
            SqlParameter param;

            if(Size > 0)
            {
                param = new SqlParameter(parameterName, DbType, Size);
            }
            else
            {
                param = new SqlParameter(parameterName, DbType);
            }
            param.Direction = Direction;
            if (!(Direction == ParameterDirection.Output && Value == null))
            {
                param.Value = Value;
            }
            return param;
        }

To perform a task similar to the first one listed, and calling a stored procedure that takes a single parameter, our code would now look something like this:

listColumnists method:
      public SqlDataReader listColumnists(int howmanyrows)
        {

            // Create Instance of Connection and Command Object
            SqlConnection conn = new SqlConnection(DAL.ConnectionString);
            SqlCommand cmd = new SqlCommand("sp_ListColumnists", conn);
            cmd.CommandType = CommandType.StoredProcedure;

            cmd.Parameters.Add(pMaker("@how_many", SqlDbType.Int, 4, ParameterDirection.Input, howmanyrows));

            // Execute the command
            conn.Open();
            SqlDataReader result = cmd.ExecuteReader(CommandBehavior.CloseConnection);

            // Return the datareader result
            return result;
        }

Now, this may not seem like it's much help, but consider the difference it makes when you call a function like this one:

insertColumnist method:
      public int insertColumnist(String url, String featured_link, String title, String description, DateTime join_date,
            bool isIndexed, bool isTaxIdOnFile, String notes, String bio, String username,
            int user_id)
        {
            SqlConnection conn = new SqlConnection(DAL.ConnectionString);
            SqlCommand cmd = new SqlCommand("sp_InsertColumnist",conn);
            cmd.CommandType = CommandType.StoredProcedure;

            // Set bit variables to numeric equivalent
            int index_column_flag = 0;
            int tax_id_on_file = 0;

            if (isIndexed) index_column_flag = 1;
            if (isTaxIdOnFile) tax_id_on_file = 1;

            // Set up Parameters
            cmd.Parameters.Add(pMaker("@user_id", SqlDbType.Int, 4, ParameterDirection.Input, user_id));
            cmd.Parameters.Add(pMaker("@url", SqlDbType.NVarChar, 255, ParameterDirection.Input, url));
            cmd.Parameters.Add(pMaker("@featured_link", SqlDbType.NVarChar, 50, ParameterDirection.Input, featured_link));
            cmd.Parameters.Add(pMaker("@title", SqlDbType.NVarChar, 100, ParameterDirection.Input, title));
            cmd.Parameters.Add(pMaker("@description", SqlDbType.NVarChar, 8000, ParameterDirection.Input, description));
            cmd.Parameters.Add(pMaker("@join_date", SqlDbType.DateTime, 0, ParameterDirection.Input, join_date));
            cmd.Parameters.Add(pMaker("@index_column_flag", SqlDbType.Bit, 0, ParameterDirection.Input, index_column_flag));
            cmd.Parameters.Add(pMaker("@tax_id_on_file", SqlDbType.Bit, 0, ParameterDirection.Input, tax_id_on_file));
            cmd.Parameters.Add(pMaker("@notes", SqlDbType.NVarChar, 8000, ParameterDirection.Input, notes));
            cmd.Parameters.Add(pMaker("@bio", SqlDbType.NVarChar, 8000, ParameterDirection.Input, bio));
            cmd.Parameters.Add(pMaker("@username", SqlDbType.NVarChar, 50, ParameterDirection.Input, username));
            cmd.Parameters.Add(pMaker("@columnist_id", SqlDbType.Int, 4, ParameterDirection.Input, null));

            try{
                conn.Open();
                cmd.ExecuteNonQuery();
            }
            catch(Exception e){
                throw e;
            }
            finally{
                if (conn.State == ConnectionState.Open)
                    conn.Close();
            }
            return Int32.Parse(cmd.Parameters["@columnist_id"].Value.ToString());
        }

Apart from this small helper function, the only other thing I put in my DAL layer today is the connection string, which I make a static variable so that I can reference it without instantiating my DAL class. In the future, I may make additions to this class that I will want to use across all of my different DAL classes, so I have all of my DAL classes inherit from this base class. My complete DAL class is listed below:

DAL.cs
using System;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Collections;
using System.Reflection;
using System.Runtime.CompilerServices;

// Assembly Info
[assembly: AssemblyTitle("ASPAlliance.DAL")]
[assembly: AssemblyDescription("Data Access Components for ASP Alliance.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("ASPAlliance")]
[assembly: AssemblyProduct("ASPAlliance Framework")]
[assembly: AssemblyCopyright("2001")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyVersion("0.1.2.1")]
[assembly: AssemblyInformationalVersion("0.1.2.1")]
[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile("")]
[assembly: AssemblyKeyName("")]

namespace ASPAlliance.DAL {

    public class DAL {
        static String m_ConnectionString;

        public static String ConnectionString{
            get{
                if (m_ConnectionString == null){
                    try{
                        m_ConnectionString = (String) ConfigurationSettings.AppSettings["DSN"];
                    }
                    catch{
                        throw new Exception("ASPAlliance DSN value not set in web.config.");
                    }

                    if (m_ConnectionString == null){
                        throw new Exception("ASPAlliance DSN value not set in web.config.");
                    }
                }
                return m_ConnectionString;
            }
        }

        protected static SqlParameter pMaker(String parameterName, SqlDbType DbType, Int32 Size,
ParameterDirection Direction, Object Value)
        {
            SqlParameter param;

            if(Size > 0)
            {
                param = new SqlParameter(parameterName, DbType, Size);
            }
            else
            {
                param = new SqlParameter(parameterName, DbType);
            }
            param.Direction = Direction;
            if (!(Direction == ParameterDirection.Output && Value == null))
            {
                param.Value = Value;
            }
            return param;
        }
    }
}

If you want to learn more about how to build a Data Access Layer in .NET, I strongly recommend you check out IBuySpy.com before anything else. As you will note, the classes and methods presented here adhere closely to the IBS recommended design.

Update: 8 June 2002
One of my readers, Eric Newton, wrote me today to let me know that the Add method of the command object returns a reference to the newly added procedure, something I was not aware of. What does this mean? Well, it means that you can add parameters and specify their values in a single line without resorting to writing your own helper function (like pMaker) to do so. Here's an example he sent me:

oCmd.Parameters.Add("@site_id", SqlDbType.Int).Value = siteId;
So, although I haven't actually had a chance to test this method out, and while it seems to me that it doesn't cover the case of output parameters, it is definitely something worth remembering. Thanks, Eric!





ASP.NET Developer's Cookbook, By Steven Smith, Rob Howard, ASPAlliance.com 

ASP.NET By Example, By Steven Smith 




Steven Smith, MCSE + Internet (4.0)
Last Modified: 6/12/2009 10:58:21 AM
History: 6/12/2009 10:58:21 AM