Click here to Skip to main content
Click here to Skip to main content

Database Factory Using C# Generics

, 8 Oct 2006 CPOL
Rate this:
Please Sign up or sign in to vote.
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)

Share

About the Author

SikeMullivan
Web Developer
United States United States
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.

Comments and Discussions

 
GeneralMy vote of 2 PinmemberJasonShort1-Mar-10 4:42 
GeneralPlease Help PinmemberRENJUR20-Jun-08 0:00 
GeneralNice Article Pinmemberjoeyaeb5-Jun-08 3:04 
Generalexample PinmemberMember 449213025-Apr-08 7:45 
GeneralDouble check work PinmemberMitchell D. Geere22-May-07 0:16 
Generalneed help on this PinmemberMember #361490521-Mar-07 20:37 
GeneralNice work Pinmembertomstrummer14-Oct-06 7:47 
GeneralPoor Approach Pinmembertkrafael_net9-Oct-06 5:05 
GeneralRe: Poor Approach PinmemberSikeMullivan9-Oct-06 5:34 
GeneralRe: Poor Approach Pinmemberpganathe30-Jan-07 2:51 
GeneralRe: Poor Approach Pinmembertkrafael_net30-Jan-07 6:26 
GeneralRe: Poor Approach Pinmemberpganathe30-Jan-07 19:16 

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.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.1411028.1 | Last Updated 8 Oct 2006
Article Copyright 2006 by SikeMullivan
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid