Implementing a Generic Data Access Layer in ADO.NET

Share Embed Donate


Short Description

Download Implementing a Generic Data Access Layer in ADO.NET...

Description

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 using using using using using using using using using using

System; System.Collections.Generic; System.Text; System.Data; System.Configuration; System.Data.Common; System.Data.SqlClient; System.Data.OleDb; System.Data.Odbc; System.IO; 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 using using using using using using using using using

System; System.Collections.Generic; System.Text; System.Data; .Configuration; System.Configuration; System.Data.Common; System.Data.SqlClient; System.Data.OleDb; System.Data.Odbc; 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 using using using using using using using using using

System; System.Collections.Generic; System.Text; System.Data; System.Configuration; System.Data.Common; System.Data.SqlClient; System.Data.OleDb; System.Data.Odbc; 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(""+"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 [email protected]. [email protected] 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 aalso lso 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 VALUES sqlCommand.ExecuteNonQuery(); sqlCommand.CommandText = "Insert VALUES sqlCommand.ExecuteNonQuery(); sqlTransaction.Commit(); //Usual code } catch(Exception e) { sqlTransaction.Rollback(); //Usual code } finally { sqlConnection.Close(); }

into Employee (EmpCode, EmpName) (1, 'Joydip')"; into Dept (DeptCode, DeptName, EmpCode) (9, 'Software', 1)";

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.

View more...

Comments

Copyright ©2017 KUPDF Inc.
SUPPORT KUPDF