Click here to Skip to main content
Licence CPOL
First Posted 8 Oct 2006
Views 69,042
Downloads 620
Bookmarked 31 times

Database Factory Using C# Generics

By | 8 Oct 2006 | Article
Database factory using C# Generics.

Introduction

This is my first article, so please bear with me. Recently at work, I've been asked to redesign a VB database layer that utilizes the Microsoft Enterprise Library. Now, the Enterprise Library is great, but it's more than what I need. In this new layer, we need to be able to easily switch our software from using one database to another. What better thing to do than use C# Generics to create an abstract database class that can be used with ODBC, OLEDB, and SQL?

Our Abstract Class

Why abstract? I'm not really sure why, but I felt like building it that way. If you have got a problem with it, let me know. First, we'll start with defining the class.

public abstract class DataBase<TParameter,TDataReader,TConnection, 
       TTransaction,TDataAdapter,TCommand> : IDisposable
where TParameter : DbParameter,IDataParameter
where TDataReader : DbDataReader,IDataReader
where TConnection : DbConnection,IDbConnection,new()
where TTransaction : DbTransaction,IDbTransaction
where TDataAdapter : DbDataAdapter,IDataAdapter,new()
where TCommand : DbCommand, IDbCommand, new()
{

The "where TConnection : DbConnection,IDbConnection,new()" requires that the type passed in for TConnection be or inherit from DbConnection and implement IDbConnection. Why did I add the interface on top of DbConnection when DbConnection implements IDbConnection? Just to show you that you can require the type to implement many of the interfaces. The "new()" means that the type being passed must have a parameterless constructor. I want that so that later in my code, I can instantiate the TConnection object that is passed in. Otherwise, you'll get a compilation error because the compiler doesn't know if the type you passed in can be instantiated.

Let's move on to actually instantiating the connection object.

protected TConnection _conn;
protected TTransaction _trans;
public DataBase(string ConnectionString)
{
    _conn = new TConnection();
    _conn.ConnectionString = ConnectionString;
}

First, we declare our connection variable of type TConnection and do the same for our transaction object. In our constructor, we can simply instantiate our object like normal because we used the new() keyword in defining our type.

Now, let's create our ExecuteDataset(), ExecuteScalar<T>(), ExecuteNonQuery(), and ExecuteDataReader() methods.

ExecuteDataSet

protected DataSet ExecuteDataSet(string StoreProcName, List<tparameter /> Params)
{
    bool internalOpen = false;
    DataSet ds = null;
    TDataAdapter da;
    TCommand cmd;

    try
    {
        ds = new DataSet();
        da = new TDataAdapter();
        cmd = new TCommand();
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.CommandText = StoreProcName;
        if (_trans != default(TTransaction))
            cmd.Transaction = _trans;
        else
            cmd.Connection = (TConnection)_conn;
        if (Params != null || Params.Count > 0)
        {
            foreach (DbParameter param in Params)
                cmd.Parameters.Add(param);
        }
        da.SelectCommand = cmd;
        if (_conn.State == ConnectionState.Closed)
        {
            _conn.Open();
            internalOpen = true;
        }
        da.Fill(ds);
        return ds;
    }
    catch (DbException DbEx)
    {
        throw DbEx;
    }
    catch (Exception ex)
    {
        throw ex;
    }
    finally
    {
        if (internalOpen)
            _conn.Close();
    }
}

Need an explanation?? It is very simple actually. Since we defined our Adapter and Command types with a new(), we can also instantiate those objects. Wondering what's with the internalOpen? Well, that's just so we don't close the connection if the connection was opened outside of this method. Especially if there is a transaction in place. We want all the transaction handling outside of this class. What's the default (TTransaction)? Basically, this means if _trans is null (null is the default for a DbTransaction object), then don't use it.

ExecuteScalar

protected T ExecuteScalar<t />(string StoreProcName, List<tparameter /> Params)
{
    bool internalOpen = false;
    TCommand cmd;
    try
    {
        cmd = new TCommand();
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.CommandText = StoreProcName;
        if (_trans != default(TTransaction))
            cmd.Transaction = _trans;
        else
            cmd.Connection = _conn;
        if (Params != null || Params.Count > 0)
        {
            foreach (DbParameter param in Params)
                cmd.Parameters.Add(param);
        }
        if (_conn.State == ConnectionState.Closed)
        {
            _conn.Open();
            internalOpen = true;
        }

        object retVal = cmd.ExecuteScalar();
        if (retVal is T)
            return (T)retVal;
        else if (retVal == DBNull.Value)
            return default(T);
        else
            throw new Exception("Object returned was of the wrong type.");
    }
    catch (DbException DbEx)
    {
        throw DbEx;
    }
    catch (Exception ex)
    {
        throw ex;
    }
    finally
    {
        if (internalOpen)
            _conn.Close();
    }
}

One of the greatest pieces of Generics is that we can define methods that return a specific type. Here, we've told our code to return whatever type we want, and if what is returned is DBNull, the return that types default. If it isn't DBNull or of the correct type, then some database developer has screwed up and needs to be scolded. :P

ExecuteNonQuery

protected int ExecuteNonQuery(string StoreProcName, List<tparameter /> Params)
{
    bool internalOpen = false;
    TCommand cmd;

    try
    {
        cmd = new TCommand();
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.CommandText = StoreProcName;
        if (_trans != default(TTransaction))
            cmd.Transaction = _trans;
        else
            cmd.Connection = _conn;
        if (Params != null || Params.Count > 0)
        {
            foreach (DbParameter param in Params)
                cmd.Parameters.Add(param);
        }
        if (_conn.State == ConnectionState.Closed)
        {
            _conn.Open();
            internalOpen = true;
        }
        return cmd.ExecuteNonQuery();
    }
    catch (DbException DbEx)
    {
        throw DbEx;
    }
    catch (Exception ex)
    {
        throw ex;
    }
    finally
    {
        if (internalOpen)
            _conn.Close();
    }
}

Easily understood.

ExecuteReader

protected TDataReader ExecuteReader(string StoreProcName, List<tparameter /> Params)
{
    bool internalOpen = false;
    TCommand cmd;

    try
    {
        cmd = new TCommand();
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.CommandText = StoreProcName;
        if (_trans != default(TTransaction))
            cmd.Transaction = _trans;
        else
            cmd.Connection = _conn;
        if (Params != null || Params.Count > 0)
        {
            foreach (DbParameter param in Params)
                cmd.Parameters.Add(param);
        }
        if (_conn.State == ConnectionState.Closed)
        {
            _conn.Open();
            internalOpen = true;
        }
        return (TDataReader)cmd.ExecuteReader();
    }
    catch (DbException DbEx)
    {
        throw DbEx;
    }
    catch (Exception ex)
    {
        throw ex;
    }
    finally
    {
        if (internalOpen)
            _conn.Close();
    }
}

In this method, our DataReader returned is of the type that was defined with our class.

Transaction Logic

public bool BeginTransaction()
{
    if (_conn != null && _conn.State == ConnectionState.Closed && _trans == null)
    {
        _conn.Open();
        _trans = (TTransaction)_conn.BeginTransaction();
        return true;
    }
    return false;
}

public bool RollBackTransaction()
{
    if (_conn != null && _conn.State == ConnectionState.Open && _trans != null)
    {
        _trans.Rollback();
        _conn.Close();
    
        _trans.Dispose();
        _trans = default(TTransaction);
        return true;
    }
    return false;
}

public bool CommitTransaction()
{
    if (_conn != null && _conn.State == ConnectionState.Open && _trans != null)
    {
        _trans.Commit();
        _conn.Close();
        _trans.Dispose();
        _trans = default(TTransaction);
        return true;
    }
    return false;
}

public abstract bool RollBackTransaction(string SavePointName);
public abstract bool SaveTransactionPoint(string SavePointName);

Here we've added our transaction logic!!! Easy!! But I haven't tested it yet so I'm not sure if it works. Ha ha. I just wrote all of this this morning and just had to get it out as soon as possible to you all. We have abstract methods because OLEDB and ODBC don't support SavePoints as of now. We'll only use these methods in our SQL database.

Inheriting our DataBase Class

using System;
using System.Collections.Generic;
using System.Text;
using System.Data.Common;
using System.Data;
namespace Global.Data
{
    public class MyDataBaseLayer<TParameter, TDataReader, TConnection, 
           TTransaction, TDataAdapter, TCommand> : DataBase<TParameter, 
           TDataReader, TConnection, TTransaction, TDataAdapter, TCommand> 
    where TParameter : DbParameter, IDataParameter,new()
    where TDataReader : DbDataReader, IDataReader
    where TConnection : DbConnection, IDbConnection, new()
    where TTransaction : DbTransaction, IDbTransaction
    where TDataAdapter : DbDataAdapter, IDataAdapter, new()
    where TCommand : DbCommand, IDbCommand, new()
    {
        protected List<TParameter> _params;
        public MyDataBaseLayer(string ConnectionString):base(ConnectionString)
        {}
        public Guid InsertNeMember(string MemberName)
        {
            TParameter paramMemberName = new TParameter();
            paramMemberName.ParameterName = "@memberName";
            paramMemberName.Value = MemberName;
            paramMemberName.DbType = System.Data.DbType.VarNumeric;
            _params = new List<TParameter>();
            _params.Add(paramMemberName);
            return ExecuteScalar<Guid>("proc_Member_Insert",_params);
        }
        public override bool RollBackTransaction(string SavePointName)
        {
            throw new Exception("The method or operation is not implemented.");
        }
        public override bool SaveTransactionPoint(string SavePointName)
        {
            throw new Exception("The method or operation is not implemented.");
        }
    }
}

using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;
using System.Data;
namespace Global.Data
{
    public class SqlDataBase : DataBase<SqlParameter,SqlDataReader,
           SqlConnection,SqlTransaction,SqlDataAdapter,SqlCommand>
    {
        public SqlDataBase(string ConnectionString)
        : base(ConnectionString)
        {
        }
        public override bool RollBackTransaction(string SavePointName)
        {
            if (_conn != null && _conn.State == ConnectionState.Open && _trans != null)
            {
                _trans.Rollback(SavePointName);
                return true;
            }
            return false;
        }
        public override bool SaveTransactionPoint(string SavePointName)
        {
            if (_conn != null && _conn.State == ConnectionState.Open && _trans != null)
            {
                _trans.Save(SavePointName);
                return true;
            }
            return false;
        }
    }
}

using System;
using System.Collections.Generic;
using System.Text;
using System.Data.Odbc;
using System.Data;
namespace Global.Data
{
    public class OdbcDataBase : DataBase<OdbcParameter,OdbcDataReader,
                 OdbcConnection,OdbcTransaction,OdbcDataAdapter,OdbcCommand>
    {
        public OdbcDataBase(string ConnectionString)
        : base(ConnectionString)
        {
        }
        public override bool RollBackTransaction(string SavePointName)
        {
            throw new Exception("The method or operation is not implemented.");
        }
        public override bool SaveTransactionPoint(string SavePointName)
        {
            throw new Exception("The method or operation is not implemented.");
        }
    }
}


using System;
using System.Collections.Generic;
using System.Text;
using System.Data.OleDb;
using System.Data;
namespace Global.Data
{
    class OleDbDataBase : DataBase<OleDbParameter,OleDbDataReader, 
          OleDbConnection,OleDbTransaction,OleDbDataAdapter,OleDbCommand>
    {
        public OleDbDataBase(string ConnectionString)
        : base(ConnectionString)
        {
        }
        public override bool RollBackTransaction(string SavePointName)
        {
            throw new Exception("The method or operation is not implemented.");
        }
        public override bool SaveTransactionPoint(string SavePointName)
        {
            throw new Exception("The method or operation is not implemented.");
        }
    }
}

Conclusion

If you don't find this a good resolution, don't just give me a bad vote, leave me a comment and an explanation. Let me know what you think. I like to know others' views.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

SikeMullivan

Web Developer

United States United States

Member

I'm a 21 year old .NET 2.0 Developer from STL. You name it, I can do it. I'm not your typical geek so if you start talking Star Wars i'll put my headphones on. My goal is to become a known .NET Architect by age 25.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralMy vote of 2 PinmemberJasonShort3:42 1 Mar '10  
GeneralGood article buddy. ... Pinmembervchauhan_me2:55 11 Aug '08  
GeneralPlease Help PinmemberRENJUR23:00 19 Jun '08  
GeneralNice Article Pinmemberjoeyaeb2:04 5 Jun '08  
Generalexample PinmemberMember 44921306:45 25 Apr '08  
GeneralDouble check work PinmemberMitchell D. Geere23:16 21 May '07  
Generalneed help on this PinmemberMember #361490519:37 21 Mar '07  
GeneralNice work Pinmembertomstrummer6:47 14 Oct '06  
GeneralPoor Approach Pinmembertkrafael_net4:05 9 Oct '06  
GeneralRe: Poor Approach PinmemberSikeMullivan4:34 9 Oct '06  
GeneralRe: Poor Approach Pinmemberpganathe1:51 30 Jan '07  
GeneralRe: Poor Approach Pinmembertkrafael_net5:26 30 Jan '07  
GeneralRe: Poor Approach Pinmemberpganathe18:16 30 Jan '07  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web02 | 2.5.120517.1 | Last Updated 8 Oct 2006
Article Copyright 2006 by SikeMullivan
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid