Вы находитесь на странице: 1из 25

Implementing a Generic Data Access Layer

in ADO.NET Part 1
06. May, 2010 0 Comments by admin

A Data Access Layer (DAL) is an integral part in the design of any application. There are plenty
of articles that discuss how we an implement a DAL using ADO.NET. Most of these have
constraints in the sense that they are not generic in nature. In other words, they are not provider
independent. This series of articles will discuss the implementation of a generic, i.e., a provider
independent Data Access
ccess Layer in ADO.NET. The basic prerequisite to learning this article is a
proper understanding of ADO.NET and good coding skills in C#. I will present the code
examples in this article in C#. However with little effort, you can twist it over to VB.NET as a
well.

The Strategies Involved in Creating a Data Access Layer

Let us first understand what the necessities are for building such a layer. I would rather start by
discussing how an application designed using ADO.NET actually connects to the database and
performs
erforms the CRUD (Create, Read, Update and Delete) operations.

First, you need to open the connection using a database provider. Fine, but what is a provider
anyway? A provider is responsible for connecting to a specific database. Why specific? The
reason is that a provider for an Oracle database cannot be used to connect to a SQL Server
database and vice-versa.
versa. Next, you need a command object that can be used to execute the
database commands of your choice. This is followed by the usage of a DataReader or a DataSet
or a DataTable instance to retrieve data (if you are performing a Read operation) from the
database table. When you use a DataSet, you need a DataAdapter as a bridge between the actual
database and the DataSet instance.

Implementing the DAL Frame


Framework

With this in mind, let us design a provider independent Data Access Layer. Let us first
understand the ADO.NET Library. The major classes that constitute the ADO.NET library are:

• Connection
• Command
• Data Reader
• Data Adapter

The corresponding interfaces that the above classes implement are stated below.

• IDBConnection
• IDataReader
• IDBCommand
• IDBDataAdapter

The Data Providers that make up the library are specific to a particular database that they would
connect to. These are the Data Providers that are available in ADO.NET.

• SQL Server Data Provider


• Oracle Data Provider
• ODBC Data Provider
• OleDB Data Provider

Now we are all set to implement our DAL. The major components that constitute our DAL block
are:

• ProviderType (Enum)
• DatabaseConnectionState (Enum)
• StoredProcedureParameterDirection (Enum)
• DBManager (Class)
• DBHelper (Class)

We will start our discussion with the enum data type that would contain the data provider types
in it. These provider types relate to the databases that we will be connecting to, depending our
requirements. The following code snippet illustrates the ProviderType enum that contains four
values that correspond to a specific data provider.

public enum ProviderType { SqlServer, OleDb, Oracle, ODBC, ConfigDefined }

Now, there may be situations where you might need to either keep the database connection state
open or close after a database operation is over. As an example, after you read data into a
DataReader instance from the underlying database, you might need to keep the connection state
open for subsequent operations. You may also need to close it if it is no longer used. Keeping
this in mind, let us have an enum data type that houses two values that correspond to the
database connection states that we just discussed about. The following is the code for the enum
called DatabaseConnectionState.

public enum DatabaseConnectionState { KeepOpen, CloseOnExit }

When you are executing the stored procedures, you might want to send data to the database or
retrieve the same from the database. Accordingly, we have another enum called
StoredProcedureParameterDirection that contains values that correspond to the parameter
directions for the stored procedures that we would execute with the help of our DAL. The
following is the code for this enum.

public enum StoredProcedureParameterDirection


{ Input, InputOutput, Output, ReturnValue }

We need a factory class that would return a DbProviderFactory type instance or a


DbDataAdapter type instance depending on the data provider that we are using. This class
contains factory methods that typically are static methods. What is a static method, anyway? This
is an often misunderstood concept but a very important one. Well, a static method, often called a
shared method (it is shared by all instances of the class that it belongs to) belongs to the class and
a non-static method belongs to an object of a class. That is, a non-static method can only be
called on an object of a class that it belongs to. A static method can however be called both on
the class as well as an object of the class. Further, a static method can access the static members
of a class only unlike a non-static method that can access both static and non-static members.
These static methods in the DBFactory class accept a reference to the ProviderType enum that
denotes the data provider type in use.

The next class in our discussion is the DBFactory class, designed on the factory design pattern.
Before we discuss the DBFactory class and its intent, let us understand what a factory design
pattern is. What is a factory design pattern? The Factory pattern is responsible for providing an
interface for the creation of objects, but allows the inherited classes to decide on the appropriate
time of these instantiations.

The following is the source code for our DBFactory class. It contains two static methods called
GetProvider and GetDataAdapter both of which accept an instance of the database provider type
enum, i.e., ProviderType.

using System.Data.Common;
using System.Data.SqlClient;
using System.Data.OleDb;
using System.Data.Odbc;
using System.Data.OracleClient;
using System.Collections.Generic;
using System.Text;
namespace DataAccessLayer
{
internal class DBFactory
{
private static DbProviderFactory objFactory = null;
public static DbProviderFactory GetProvider(ProviderType provider)
{
switch (provider)
{
case ProviderType.SqlServer:
objFactory = SqlClientFactory.Instance;
break;
case ProviderType.OleDb:
objFactory = OleDbFactory.Instance;
break;
case ProviderType.Oracle:
objFactory = OracleClientFactory.Instance;
break;
case ProviderType.ODBC:
objFactory = OdbcFactory.Instance;
break;
}
return objFactory;
}
public static DbDataAdapter GetDataAdapter(ProviderType providerType)
{
switch (providerType)
{
case ProviderType.SqlServer:
return new SqlDataAdapter();
case ProviderType.OleDb:
return new OleDbDataAdapter();
case ProviderType.ODBC:
return new OdbcDataAdapter();
case ProviderType.Oracle:
return new OracleDataAdapter();
default:
return null;
}
}
}
}

Note that you have different providers for different databases, i.e., the database providers are all
database specific. A DataAdapter as we may recall, is a bridge between the database and the
DataSet – a set of disconnected data. Though you have various data readers depending on the
type of the data provider you are using, you have only one type of data set. Why? This is because
a data set is a disconnected in-memory set of data. The schema of the database defines the
schema of the data set.

Refer to the code example shown above. Both the methods in the code example shown above
check the value of the enum reference instance and accordingly return an appropriate
DbDataProvider or DbProviderFactory instance respectively. Such methods are actually called
factory methods.

Conclusion

In this article we have discussed how we can design and implement and generic data access
layer, i.e., one that can be used to perform database operations irrespective of the database being
used. In the next article in this series, we will discuss the Databasehelper class, i.e., the class that
actually performs the database operations.
Part 2 08. May, 2010 0 Comments by admin

In the first part of this series of articles on Data Access Layer, we have had a look at what the
strategies are, for designing
ning and implementing a Generic Data Access Layer. We have had a look
at the enumerators and the factory classes that we will be using. In this part of this article of three
part series, we will discuss how we can implement the DatabaseHelper class, one that th would be
responsible for performing the actual database operations.

The DatabaseHelper class encapsulates the various calls to the database to perform the CRUD
operations. The DBManager class that we will discuss later acts as a wrapper on top of this class.
You have various methods in the DatabaseHelper class to add parameters, to execute queries,
stored procedures, etc.

Here is the code that illustrates how the connection to the database is established based on the
provider type chosen and the command object created.

public DatabaseHelper(string connectionstring, ProviderType provider)


{
this.strConnectionString = connectionstring;
objFactory = DBFactory.GetProvider(provider);
objConnection = objFactory.CreateConnection();
objCommand = objFactory.CreateCommand();
objConnection.ConnectionString = this.strConnectionString;
objCommand.Connection = objConnection;
}

In ADO.NET, you have the following data providers.

• SQL Server Data Provider


• Oracle Data Provider
• Odbc Data Provider
• OleDB Data Provider

Note: Depending on the data provider used, you need to use the command object that is specific
to that provider. Your data reader should also be specific to the data provider used. The use of
the DBFactory class as shown in the code snippet above. Note that you use the command objects
to execute the database commands that contain the SQL statements. Added to this, we will have
overloaded versions of AddParameter method to add parameters to the command objects so that
we can
an pass parameters to the database stored procedures or SQL statements. Here is the
simplest version of the AddParameter method.

internal int AddParameter(string name, object value)


{
DbParameter dbParameter = objFactory.CreateParameter();
dbParameter.ParameterName
Parameter.ParameterName = name;
dbParameter.Value = value;
return objCommand.Parameters.Add(dbParameter);
}
While the ParameterName identifies the unique name of the parameter to be passed, the Value
implies the value of the parameter passed. Hence, if the ParameterName comprises of
“@EmpName”, the Parameter’s value might be “Joydip Kanjilal”.

In order to ensure that our DataAccessLayer supports transactions, we have three methods that
enable support for transactions. Fine, but what is a transaction? A transaction is an unit of work
that is guaranteed to be executed in its entirety or not executed at all. Here are those methods.

internal void BeginTransaction()


{
if(objConnection.State == System.Data.ConnectionState.Closed)
{
objConnection.Open();
}
objCommand.Transaction = objConnection.BeginTransaction();
}

internal void CommitTransaction()


{
objCommand.Transaction.Commit();
objConnection.Close();
}

internal void RollbackTransaction()


{
objCommand.Transaction.Rollback();
objConnection.Close();
}

Note that we have methods that correspond to beginning, commiting or rolling a transaction back
to revert the changes.

We will have the following four methods for performing the CRUD (Create, Update, Read and
Delete) operations in the database. These methods are:

ExecuteScalar()
ExecuteReader()
ExecuteNonQuery()
ExecuteDataSet()

The ExecuteScalar() method is used to read one value from the database. The ExecuteReader()
method returns a DataReader instance from the database populated with rows of data. The
ExecuteNonQuery() method is used to insert, update or delete data either using SQL statements
or using stored procedures.
The following is the complete code for the DatabaseHelper class.

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Configuration;
using System.Data.Common;
using System.Data.SqlClient;
using System.Data.OleDb;
using System.Data.Odbc;
using System.IO;
using ApplicationFramework.Configuration;

namespace ApplicationFramework.DataAccessLayer
{
public class DatabaseHelper : IDisposable
{
private string strConnectionString;
private DbConnection objConnection;
private DbCommand objCommand;
private DbProviderFactory objFactory = null;
private ParameterCache parameterCache =
ParameterCache.GetParameterCache();
public DatabaseHelper(string connectionstring, ProviderType provider)
{
this.strConnectionString = connectionstring;
objFactory = DBFactory.GetProvider(provider);
objConnection = objFactory.CreateConnection();
objCommand = objFactory.CreateCommand();
objConnection.ConnectionString = this.strConnectionString;
objCommand.Connection = objConnection;
}

internal int AddParameter(string name, object value)


{
DbParameter dbParameter = objFactory.CreateParameter();
dbParameter.ParameterName = name;
dbParameter.Value = value;
return objCommand.Parameters.Add(dbParameter);
}

internal int AddParameter(DbParameter parameter)


{
return objCommand.Parameters.Add(parameter);
}

internal int AddParameter(string name, StoredProcedureParameterDirection


parameterDirection)
{
DbParameter parameter = objFactory.CreateParameter();
parameter.ParameterName = name;
parameter.Value = String.Empty;
parameter.DbType = DbType.String;
parameter.Size = 50;
switch (parameterDirection)
{
case StoredProcedureParameterDirection.Input:
parameter.Direction = System.Data.ParameterDirection.Input;
break;
case StoredProcedureParameterDirection.Output:
parameter.Direction = System.Data.ParameterDirection.Output;
break;
case StoredProcedureParameterDirection.InputOutput:
parameter.Direction=System.Data.ParameterDirection.InputOutput;
break;
case StoredProcedureParameterDirection.ReturnValue:
parameter.Direction=System.Data.ParameterDirection.ReturnValue;
break;
}
return objCommand.Parameters.Add(parameter);
}

internal int AddParameter(string name, object value,


StoredProcedureParameterDirection parameterDirection)
{
DbParameter parameter = objFactory.CreateParameter();
parameter.ParameterName = name;
parameter.Value = value;
parameter.DbType = DbType.String;
parameter.Size = 50;
switch (parameterDirection)
{
case StoredProcedureParameterDirection.Input:
parameter.Direction = System.Data.ParameterDirection.Input;
break;
case StoredProcedureParameterDirection.Output:
parameter.Direction = System.Data.ParameterDirection.Output;
break;
case StoredProcedureParameterDirection.InputOutput:
parameter.Direction = System.Data.ParameterDirection.InputOutput;
break;
case StoredProcedureParameterDirection.ReturnValue:
parameter.Direction = System.Data.ParameterDirection.ReturnValue;
break;
}
return objCommand.Parameters.Add(parameter);
}

internal int AddParameter(string name, StoredProcedureParameterDirection


parameterDirection, int size, DbType dbType)
{
DbParameter parameter = objFactory.CreateParameter();
parameter.ParameterName = name;
parameter.DbType = dbType;
parameter.Size = size;
switch (parameterDirection)
{
case StoredProcedureParameterDirection.Input:
parameter.Direction = System.Data.ParameterDirection.Input;
break;
case StoredProcedureParameterDirection.Output:
parameter.Direction = System.Data.ParameterDirection.Output;
break;
case StoredProcedureParameterDirection.InputOutput:
parameter.Direction=System.Data.ParameterDirection.InputOutput;
break;
case StoredProcedureParameterDirection.ReturnValue:
parameter.Direction=System.Data.ParameterDirection.ReturnValue;
break;
}
return objCommand.Parameters.Add(parameter);
}

internal int AddParameter(string name, object value,


StoredProcedureParameterDirection parameterDirection,
int size, DbType dbType)
{
DbParameter parameter = objFactory.CreateParameter();
parameter.ParameterName = name;
parameter.Value = value;
parameter.DbType = dbType;
parameter.Size = size;
switch (parameterDirection)
{
case StoredProcedureParameterDirection.Input:
parameter.Direction = System.Data.ParameterDirection.Input;
break;
case StoredProcedureParameterDirection.Output:
parameter.Direction = System.Data.ParameterDirection.Output;
break;
case StoredProcedureParameterDirection.InputOutput:
parameter.Direction = System.Data.ParameterDirection.InputOutput;
break;
case StoredProcedureParameterDirection.ReturnValue:
parameter.Direction = System.Data.ParameterDirection.ReturnValue;
break;
}
return objCommand.Parameters.Add(parameter);
}

internal DbCommand Command { get { return objCommand; } }

internal DbConnection Connection { get { return objConnection; } }

internal void BeginTransaction()


{
if(objConnection.State == System.Data.ConnectionState.Closed)
{
objConnection.Open();
}
objCommand.Transaction = objConnection.BeginTransaction();
}
internal void CommitTransaction()
{
objCommand.Transaction.Commit();
objConnection.Close();
}

internal void RollbackTransaction()


{
objCommand.Transaction.Rollback();
objConnection.Close();
}

internal int ExecuteNonQuery(string query)


{
return ExecuteNonQuery(query, CommandType.Text,
DatabaseConnectionState.CloseOnExit);
}

internal int ExecuteNonQuery(string query, CommandType commandtype)


{
return ExecuteNonQuery(query, commandtype,
DatabaseConnectionState.CloseOnExit);
}

internal int ExecuteNonQuery(string query,


DatabaseConnectionState connectionstate)
{
return ExecuteNonQuery(query, CommandType.Text, connectionstate);
}

internal int ExecuteNonQuery(string query, CommandType commandtype,


DatabaseConnectionState connectionstate)
{
objCommand.CommandText = query;
objCommand.CommandType = commandtype;
int i = -1;
try
{
If (objConnection.State == System.Data.ConnectionState.Closed)
{
objConnection.Open();
}
i = objCommand.ExecuteNonQuery();
}
catch
{
throw;
}
finally
{
if(connectionstate == DatabaseConnectionState.CloseOnExit)
{
objConnection.Close();
}
}
return i;
}
internal object ExecuteScalar(string query)
{
return ExecuteScalar(query, CommandType.Text,
DatabaseConnectionState.CloseOnExit);
}

internal object ExecuteScalar(string query, CommandType commandtype)


{
return ExecuteScalar(query, commandtype,
DatabaseConnectionState.CloseOnExit);
}

internal object ExecuteScalar(string query,


DatabaseConnectionState connectionstate)
{
return ExecuteScalar(query, CommandType.Text, connectionstate);
}

internal object ExecuteScalar(string query, CommandType commandtype,


DatabaseConnectionState connectionstate)
{
objCommand.CommandText = query;
objCommand.CommandType = commandtype;
object o = null;
try
{
if(objConnection.State == System.Data.ConnectionState.Closed)
{
objConnection.Open();
}
o = objCommand.ExecuteScalar();
}
catch
{
throw;
}
finally
{
objCommand.Parameters.Clear();
if(connectionstate == DatabaseConnectionState.CloseOnExit)
{
objConnection.Close();
}
}
return o;
}

internal DbDataReader ExecuteReader(string query)


{
return ExecuteReader(query, CommandType.Text,
DatabaseConnectionState.CloseOnExit);
}
internal DbDataReader ExecuteReader(string query, CommandType commandtype)
{
return ExecuteReader(query, commandtype,
DatabaseConnectionState.CloseOnExit);
}

internal DbDataReader ExecuteReader(string query,


DatabaseConnectionState connectionstate)
{
return ExecuteReader(query, CommandType.Text, connectionstate);
}

internal DbDataReader ExecuteReader(string query, CommandType commandtype,


DatabaseConnectionState connectionstate)
{
objCommand.CommandText = query;
objCommand.CommandType = commandtype;
DbDataReader reader = null;
try
{
if(objConnection.State == System.Data.ConnectionState.Closed)
{
objConnection.Open();
}
if(connectionstate == DatabaseConnectionState.CloseOnExit)
{
reader = objCommand.ExecuteReader(CommandBehavior.CloseConnection);
}
else
{
reader = objCommand.ExecuteReader();
}
}
catch { }
finally { objCommand.Parameters.Clear(); }

return reader;
}

internal DataSet ExecuteDataSet(string query)


{
return ExecuteDataSet(query, CommandType.Text,
DatabaseConnectionState.CloseOnExit);
}

internal DataSet ExecuteDataSet(string query, CommandType commandtype)


{
return ExecuteDataSet(query, commandtype,
DatabaseConnectionState.CloseOnExit);
}

internal DataSet ExecuteDataSet(string query,


DatabaseConnectionState connectionstate)
{
return ExecuteDataSet(query, CommandType.Text, connectionstate);
}
internal DataSet ExecuteDataSet(string query, CommandType commandtype,
DatabaseConnectionState connectionstate)
{
DbDataAdapter adapter = objFactory.CreateDataAdapter();
objCommand.CommandText = query;
objCommand.CommandType = commandtype;
adapter.SelectCommand = objCommand;
DataSet ds = new DataSet();
try
{
adapter.Fill(ds);
}
catch
{
throw;
}
finally
{
objCommand.Parameters.Clear();
if(connectionstate == DatabaseConnectionState.CloseOnExit)
{
if(objConnection.State == System.Data.ConnectionState.Open)
{
objConnection.Close();
}
}
}
return ds;
}

public void Dispose()


{
if (objConnection.State == ConnectionState.Open)
{
objConnection.Close();
objConnection.Dispose();
}
objCommand.Dispose();
}

internal IDataReader ExecuteReader(string storedProcedureName,


params object[] parameters)
{
objCommand.CommandText = storedProcedureName;
objCommand.CommandType = CommandType.StoredProcedure;
DbDataReader reader = null;
try
{
RetrieveParameters(objCommand);
SetParameterValues(objCommand, parameters);
if(objConnection.State == System.Data.ConnectionState.Closed)
{
objConnection.Open();
}
reader = objCommand.ExecuteReader();
}
catch
{
throw;
}
finally
{
objCommand.Parameters.Clear();
}
return reader;
}

internal void SetParameterValues(DbCommand objCommand, object[] parameters)


{
int index = 0;
for (int i = 0; i < parameters.Length; i++)
{
DbParameter parameter = objCommand.Parameters[i + index];
SetParameterValue(objCommand, parameter.ParameterName, parameters[i]);
}
}

internal virtual void SetParameterValue(DbCommand dbCommand,


string parameterName, object value)
{
dbCommand.Parameters[parameterName].Value =
(value == null) ? DBNull.Value : value;
}

internal void RetrieveParameters(DbCommand dbCommand)


{
if (parameterCache.ContainsParameters(Connection.ConnectionString,
dbCommand.CommandText))
{
DbParameter[] parameters =
parameterCache.GetParameters(Connection.ConnectionString,
dbCommand.CommandText);
dbCommand.Parameters.AddRange(parameters);
}
else
{
string connectionString = Connection.ConnectionString;
dbCommand.Connection = Connection;
Connection.Open();
SqlCommandBuilder.DeriveParameters(dbCommand as SqlCommand);
parameterCache.AddParameters(connectionString, dbCommand.CommandText,
dbCommand.Parameters);
}
}

internal object GetParameter(string name)


{ return objCommand.Parameters[name].Value; }
}
}
Conclusion

In the concluding part of this series we will discuss how we can create a wrapper class that
encapsulates the DBHelper class and how we can use that class to perform the various CRUD
operations in our database.

Part 3 14. May, 2010 0 Comments by admin

In this the final article of this series, I will discuss the other classes in this framework and how
we can use this framework to perform various CRUD operations in our applications.

Let us start from where we left off in the previous part of this series. Note that most of the
methods of the DatabaseHelper class have been marked as “internal” to prevent them from being
called outside of the “ApplicationFramework.DataAccessLayer” namespace.

Now we will come to the DBManager class; the wrapper class that encapsulates the calls to
another class called DBHelper that actually performs the CRUD operations on the underlying
database. The DBManager class extends the DBManagerBase abstract class. The
DBManagerBase class contains the definition for the Open () and the Close () methods and some
other public properties that are generic and can be used by any class that acts as a wrapper. We
will have a look at the DBManagerBase class first.

The following code listing shows the DBManagerBase class.

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using .Configuration;
System.Configuration;
using System.Data.Common;
using System.Data.SqlClient;
using System.Data.OleDb;
using System.Data.Odbc;
using System.IO;

namespace ApplicationFramework.DataAccessLayer
{
public abstract class DBManagerBase
{
protected DatabaseHelper da
databaseHelper = null;
protected DbDataReader dbDataReader = null;
protected DataSet dataSet = null;
protected ProviderType providerType;
protected String connectionString = String.Empty;
protected bool isOpen = false;
public bool IsOpen
{
get { return isOpen; }
set { isOpen = value; }
}

public string ConnectionString


{
get { return connectionString; }
set { connectionString = value; }
}

public DbConnection Connection


{
get { return databaseHelper.Connection; }
}

public DbCommand Command


{
get { return databaseHelper.Command; }
}

public ProviderType DBProvider


{
set { providerType = value; }
get { return providerType; }
}

public DataSet DBSet


{
get { return dataSet; }
}

public DbDataReader DBReader


{
get { return dbDataReader; }
}

protected void Open(string connectionString)


{
databaseHelper = new DatabaseHelper(connectionString, DBProvider);
}

protected void Close()


{
if (dbDataReader != null)
if (!dbDataReader.IsClosed)
dbDataReader.Close();
databaseHelper.Dispose();
}

public void BeginTransaction() { databaseHelper.BeginTransaction(); }


public void CommitTransaction() { databaseHelper.CommitTransaction(); }
public void RollbackTransaction(){ databaseHelper.RollbackTransaction(); }
}
}
Note that the DBManagerBase class contains the most common methods that are required. You
can Open or Close a connection, Begin, Commit or Rollback transactions, etc. These methods
would remain the same and are mandatory in this context even if you decide to have another
version of the DBManager class with some more methods implemented it.

The DBManager class that extends the DBManagerBase abstract class contains a list of methods
that can be used to execute stored procedures, queries and return DataSet instance or DataReader
instances as well. You can opt for keeping your connection open after the ExecuteReader method
is called so that you can use the live connection in the subsequent operations that you need to
perform on your database. The methods names in the DBManager class relate to the operations
that they are meant to perform. I feel not you will have any problems understanding what each of
these methods are supposed to do.

Then, you have the AddParameter method that can be used to add parameters to your stored
procedure so that at the time of invoking the procedure, you can pass the parameters along. The
connection string that we need to use to connect to our database can be set using the
ConnectionString public property. The connection string can typically be stored in your
configuration file and the DBManager class can read the configuration file to retrieve the
connection string.

The provider type can be set using the ProviderType enum. Fine, but, where will these values be
set, i.e., how can we call the DBManager and from where? Confused? Hang on. Let us have a
look at the DBManager class followed by how we can use this class to perform CRUD
operations.

The following code listing depicts the DBManager class.

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Configuration;
using System.Data.Common;
using System.Data.SqlClient;
using System.Data.OleDb;
using System.Data.Odbc;
using System.IO;

namespace ApplicationFramework.DataAccessLayer
{
public sealed class DBManager : DBManagerBase
{
public void OpenConnection()
{
connectionString =
ConfigurationSettings.AppSettings["ConnectionString"].ToString();
base.Open(connectionString);
}
public void OpenConnection(String connectionString)
{
base.Open(connectionString);
base.IsOpen = true;
}

public void CloseConnection()


{
if (base.isOpen)
base.Close();
base.IsOpen = false;
}

public int AddParameter(string name, object value)


{
return databaseHelper.AddParameter(name, value);
}

public int AddParameter(string name,


StoredProcedureParameterDirection parameterDirection)
{
return databaseHelper.AddParameter(name, parameterDirection);
}

public int AddParameter(string name, object value,


StoredProcedureParameterDirection parameterDirection)
{
return databaseHelper.AddParameter(name, value, parameterDirection);
}

public int AddParameter(string name, StoredProcedureParameterDirection


parameterDirection, int size, DbType dbType)
{
return databaseHelper.AddParameter(name, parameterDirection,
size, dbType);
}

public int AddParameter(string name, object value,


StoredProcedureParameterDirection parameterDirection,
int size, DbType dbType)
{
return databaseHelper.AddParameter(name, value, parameterDirection,
size, dbType);
}

public object GetParameter(string name)


{
return databaseHelper.GetParameter(name);
}

public DbDataReader ExecuteReader(string query)


{
this.dbDataReader = databaseHelper.ExecuteReader(query);
return this.dbDataReader;
}
public DbDataReader ExecuteReader(string query, CommandType commandtype)
{
this.dbDataReader = databaseHelper.ExecuteReader(query, commandtype,
DatabaseConnectionState.CloseOnExit);
return this.dbDataReader;
}

public IDataReader ExecuteReader(string storedProcedureName,


params object[] parameters)
{
this.dbDataReader = (DbDataReader) databaseHelper.ExecuteReader(
storedProcedureName, parameters);
return this.dbDataReader;
}

public DbDataReader ExecuteReader(string query, CommandType commandtype,


DatabaseConnectionState connectionstate)
{
this.dbDataReader = databaseHelper.ExecuteReader(query, commandtype,
connectionstate);
return this.dbDataReader;
}

public DbDataReader ExecuteReader(string query,


DatabaseConnectionState connectionstate)
{
this.dbDataReader = databaseHelper.ExecuteReader(query, connectionstate);
return this.dbDataReader;
}

public object ExecuteScalar(string query)


{
return databaseHelper.ExecuteScalar(query);
}

public object ExecuteScalar(string query, CommandType commandtype)


{
return databaseHelper.ExecuteScalar(query, commandtype);
}

public object ExecuteScalar(string query,


DatabaseConnectionState connectionstate)
{
return databaseHelper.ExecuteScalar(query, connectionstate);
}

public object ExecuteScalar(string query, CommandType commandtype,


DatabaseConnectionState connectionstate)
{
return databaseHelper.ExecuteScalar(query, commandtype, connectionstate);
}

public DataSet ExecuteDataSet(string query)


{
this.dataSet = databaseHelper.ExecuteDataSet(query);
return this.dataSet;
}
public DataSet ExecuteDataSet(string query, CommandType commandtype)
{
this.dataSet = databaseHelper.ExecuteDataSet(query, commandtype);
return this.dataSet;
}

public int ExecuteNonQuery(string query, CommandType commandtype)


{
return databaseHelper.ExecuteNonQuery(query, commandtype);
}

public int ExecuteNonQuery(string query, CommandType commandtype,


DatabaseConnectionState databaseConnectionState)
{
return databaseHelper.ExecuteNonQuery(query, commandtype,
databaseConnectionState);
}
}
}

Using the DBManager class

You can make use of the DBManager class as shown in the code snippet below.

DBManager dbManager = new DBManager();


dbManager.OpenConnection();
dbManager.ExecuteReader("Select * from employee");

while (dbManager.DBReader.Read())
Response.Write(dbManager.DBReader[“EmpName”].ToString());
dbManager.CloseConnection();

Note that the OpenConnection and the CloseConnection methods of the DBManager class
invoke the Open and the Close methods of the DBManagerBase class internally.

Similarly, you can use the DBManager class to insert data as shown in the code snippet below.

DBManager dbManager = new DBManager();


String sql = "insert into employee (EmpCode, EmpName)
values ('E001''Joydip')";
try
{ dbManager.OpenConnection();
dbManager.ExecuteNonQuery(sql,CommandType.Text);
}
catch(Exception e)
{ HttpContext.Current.Response.Write(e); }
finally
{
dbManager.CloseConnection();
HttpContext.Current.Response.Write("<BR>"+"1 record added...");
}
Conclusion

We have had a look at the strategies involved in the design and implementation of a Generic
DAL framework in this series of articles on the Data Access Layer. Let me know kno your
views/comments by sending me mails at joydipkanjilal@yahoo.com.
joydipkanjilal@yahoo.com I blog at
http://aspadvice.com/blogs/joydip
http://aspadvice.com/blogs/joydip.

Working with ADO.NET Transactions


25. Jul, 2010 0 Comments by admin

A transaction is a group of operations combined into a logical unit of work that is either
guaranteed to be executed as a whole or rolled back. Transactions help the database in satisfying
all the ACID (Atomic, Consistent, Isolated, and Durable). Transaction processing is an
indispensible part of ADO.NET. It guarantees that a block of statements will either be executed
e
in its entirety or rolled back,( i.e., none of the statements will be executed). Transaction
processing has improved a lot in ADO.NET 2.0. This article discusses how we can work with
transactions in both ADO.NET 1.1 and 2.0.

Implementing Transactions
ns in ADO.NET

Note that in ADO.NET, the transactions are started by calling the BeginTransaction method of
the connection class. This method returns an object of type SqlTransaction. Other ADO.NET
connection classes like OleDbConnection, OracleConnection aalsolso have similar methods. Once
you are done executing the necessary statements within the transaction unit/block, make a call to
the Commit method of the given SqlTransaction object, or you can roll back the transaction
using the Rollback method, depending on your requirements (if any error occurs when the
transaction unit/block was executed).

To work with transactions in ADO.NET, you require an open connection instance and a
transaction instance. Then you need to invoke the necessary methods as stated later
late in this
article. Transactions are supported in ADO.NET by the SqlTransaction class that belongs to the
System.Data.SqlClient namespace.

The two main properties of this class are as follows:

• Connection: This indicates the SqlConnection instance that the transaction instance is
associated with
• IsolationLevel: This specifies the IsolationLevel of the transaction

The following are the methods of this class that are noteworthy:
Commit() This method is called to commit the transaction
Rollback() This method can be invoked to roll back a transaction. Note that a transaction can
only be rolled back after it has been committed.
Save() This method creates a save point in the transaction. This save point can be used to
rollback a portion of the transaction at a later point in time. The following are the steps to
implement transaction processing in ADO.NET.

• Connect to the database


• Create a SqlCommand instance with the necessary parameters
• Open the database connection using the connection instance
• Call the BeginTransaction method of the Connection object to mark the beginning of the
transaction
• Execute the sql statements using the command instance
• Call the Commit method of the Transaction object to complete the
transaction, or the Rollback method to cancel or abort the transaction
• Close the connection to the database

The following code snippet shows how we can implement transaction processing using
ADO.NET in our applications.

string connectionString = ...; //Some connection string


SqlConnection sqlConnection = new SqlConnection(connectionString);
sqlConnection.Open();

SqlTransaction sqlTransaction = sqlConnection.BeginTransaction();

SqlCommand sqlCommand = new SqlCommand();


sqlCommand.Transaction = sqlTransaction;

try
{
sqlCommand.CommandText = "Insert into Employee (EmpCode, EmpName)
VALUES (1, 'Joydip')";
sqlCommand.ExecuteNonQuery();
sqlCommand.CommandText = "Insert into Dept (DeptCode, DeptName, EmpCode)
VALUES (9, 'Software', 1)";
sqlCommand.ExecuteNonQuery();
sqlTransaction.Commit();
//Usual code
}
catch(Exception e)
{
sqlTransaction.Rollback();
//Usual code
}
finally
{
sqlConnection.Close();
}
The next piece of code illustrates how we can use the “using” statement for the above code.
According to MSDN, the “using” statement, “defines a scope, outside of which an object or
objects will be disposed. A using statement can be exited either when the end of the using
statement is reached or if an exception is thrown and control leaves the statement block before
the end of the statement”.

using (SqlConnection sqlConnection = new SqlConnection(connectionString))


{
SqlCommand command = connection.CreateCommand();
SqlTransaction transaction = null;

try
{
sqlConnection.Open();
transaction = sqlConnection.BeginTransaction();

command.Transaction = transaction;

command.CommandText = "Insert into employee (empID, empName) values (1,


'Joydip');
command.ExecuteNonQuery();

command.CommandText = "Insert into dept (deptID,deptName,empID) values


(9,'Software',1)";
command.ExecuteNonQuery();

transaction.Commit();
}
catch(Exception ex)
{
transaction.Rollback();
throw ex;
}
finally
{
sqlConnection.Close();
}
}

The Microsoft’s ADO.NET version 2.0 added a lot of new features to its earlier counterpart to
add moer flexibility and ease of use. As far as transactions are concerned, a new namespace
called System.Transactions has been introduced that promises a significantly improved support
for distributed transactions. It contains a class called TransactionScope that can run a set of
statements. It can also determine whether the objects in the scope have support for transactions.
If the transaction has completed successfully, the changes are committed to the database else it is
rolled back. We need to specify whether the transaction block is complete by making a call to the
TransactionScope.Complete method explicitly, else, the transaction would be rolled back when
the transaction instance would be discarded by the implicit Dispose method.
The following piece of code illustrates what we have learnt so far in this section.

bool IsConsistent = false;


using (System.Transactions.TransactionScope transactionScope = new
System.Transactions.TransactionScope())
{
SqlConnection sqlConnection = newSqlConnection(connectionString);
string sqlString = "Update emp set empName = 'Joydip Kanjilal' where empID =
9";
SqlCommand cmd1 = newSqlCommand(sql, cn);
sqlConnection.Open();
cmd1.ExecuteNonQuery();
sqlConnection.Close();
transactionScope.Consistent = IsConsistent;
}

TransactionScope also has support for distributed transactions. We can


implement transactions for multiple database connections using it. The
following piece of code shows how we can implement transactional support for
multiple databases using the TransactionScope class.

using (TransactionScope transactionScope = new TransactionScope())


{
using (SqlConnection codesDatabaseConnection = new
SqlConnection(codesDatabaseConnectionString))
{
SqlCommand sqlCommandCodes = codesDatabaseConnection.CreateCommand();
sqlCommandCodes.CommandText = "Insert Into codes (codeID,codeText) values
(1,'Test')";
codesDatabaseConnection.Open();
sqlCommandCodes.ExecuteNonQuery();
codesDatabaseConnection.Close();
}

using (SqlConnection statesDatabaseConnection = new


SqlConnection(statesDatabaseConnectionString))
{
SqlCommand sqlCommandStates = statesDatabaseConnection.CreateCommand();
sqlCommandStates.CommandText = "Insert into States(stateID,stateName) values
(1, 'Test')";
codesDatabaseConnection.Open();
sqlCommandStates.ExecuteNonQuery();
statesDatabaseConnection.Close();
}

transactionScope.Complete();
}

Points to be noted

It should be noted that the SqlTransaction object returned by the BeginTransaction () method has
to be assigned to the Transaction property of the Command object; else an
InvalidOperationException will be thrown by the application when the first query is executed.
Likewise, the Connection instance should be open by invoking the Open method on it prior to
starting a new transaction; else an InvalidOperationException would be thrown. In order to
improve the performance of applications, we should try to keep the transactions (the transaction
units/blocks that contain the statements to be executed in a batch as a whole) as short as possible.
This will help minimize the lock contention and hence increase throughput. Further, we should
analyze whether or not we actually require a transaction for a batch of statements. Try not to
unnecessarily have transactional statements in you code as it might have a performance
drawback due to the reasons stated above.

Conclusion

The usage of transactions guarantee the execution of a batch of statements sequentially in its
entirety or roll them back hence preserving the database integrity and data consistency. In a
nutshell, in order to work with transactions, invoke the BeginTransaction() method of the
appropriate database connection instance and then call either the Commit() or Rollback() method
on the returned transaction object reference depending on the circumstances. However, it should
be noted that transactions hold locks and may cause contention issues; they should be as short as
it is possible. Hence, this is a major performance drawback in using transactions in our code.
But, if used properly, it can facilitate the design and implementation of robust applications with
data security and consistency. This article has discusses transactions in details in a lucid
language with code examples to illustrate the concepts.

Вам также может понравиться