Академический Документы
Профессиональный Документы
Культура Документы
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.
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.
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.
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.
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.
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.
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.
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.
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.
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;
}
return reader;
}
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.
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.
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; }
}
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.
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;
}
You can make use of the DBManager class as shown in the code snippet below.
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.
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.
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.
• 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.
The following code snippet shows how we can implement transaction processing using
ADO.NET in our applications.
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”.
try
{
sqlConnection.Open();
transaction = sqlConnection.BeginTransaction();
command.Transaction = transaction;
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.
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.