Introduction
Data access is always an unavoidable part of any software development process. There are different types of DBMS systems available in the market. And software architects consider data access as a separate layer while designing multi-tired applications. Here I am presenting a unique data access component which encapsulates the data access functionalities of ADO.NET, and some other useful utilities to use with any type of application and DB backend. One of the major advantages of this component is that, it provides a transparent interface for accessing SQL, OLEDB, ODBC, and Oracle backend. It visualizes a new concept called Parameter Engine, using which parameters for Stored Procedures etc., can be constructed easily and passed to any type of backend.
This library is simple and light weight. Anyone who has experience in .NET data access can easily understand this library. Using this component, you can very easily switch your DBMS systems without any change in your code. You will be writing your data access logic targeting the definitions in this library. During deployment, simply change the connection string in your settings, initialize the appropriate class based on another setting, and the rest will be managed by the data access component.
Structure
MS.Components.Data
IBackendSource
: Defines some base operations that can be performed on any type of backend. Connection management etc., goes here.IBackend
: Inherited from IBackendSource
and defines common data access operations supported by ADO.NET, like ExecuteNonQuery
, BeginTransaction
etc.BackendType
: Enumeration specifying the type of backend like SQL Oracle etc.ParameterEngine
: A unique class that helps the easy manipulation of DB parameters for DB commands without knowing the type of the backend.
MS.Components.Data.Base
DataAccess
: The abstract class inherited from IBackend
. This class forms the base class for any specific back end type implementation, and also provides implementation of some common operations like connection management, transaction control, command generation etc.
MS.Components.Data.SQLServer
SQLServerDB
: Derived from DataAccess
, and provides a concrete implementation of data access operations for a SQL Server data source.
MS.Components.Data.Oracle
OracleDB
: Derived from DataAccess
, and provides a concrete implementation of data access operations for an Oracle data source.
MS.Components.Data.ODBC
OdbcAccess
: Derived from DataAccess
, and provides a concrete implementation of data access operations for an ODBC data source.
MS.Components.Data.OLEDB
OleDbAccess
: Derived from DataAccess
, and provides a concrete implementation of data access operations for an OLEDB data source.
MS.Components.Data.Utilities
SQLInstancesFinder
: A static utility class that can be used to find SQL Server instances available over a network, and retrieves the information databases in it.ODBCDataSourcesFinder
: A static utility class that can be used to retrieve information about ODBC data sources set in the Control Panel.
MS.Components.Data.Controls
SQLConnectionSetupControl
: A Windows user control using which you can browse SQL Server instances and their DBs in the network and generate connection strings for them. Similar to Add Connection in the Server Explorer.
Inside the Data Access Component
A. Data Access Interfaces and Classes
The DAL is built upon two interfaces and an abstract class: IBackendSource
, IBackend
, and DataAccess
. The main purpose of these interfaces and the abstract class is to define a standard for further extension and addition of new DBMS access libraries to the component. Let's dig inside.
The purpose of these interfaces is to define a standard language for data access. Similar to ADO.NET's IDbConnection
, IDbCommand
etc. These interfaces will allow the assembly to be exposed towards COM and used with applications like VB6 etc. Also, they will help in implementing new data layer components for new types of data sources. There are two interfaces which separate the DBMS operations as connectivity and data access operations.
IBackendSource
This interface mainly defines connection management operations like specifying a connection string, specifying the backend type, opening/closing a connection etc. See the comments on each definition.
public interface IBackendSource : IDisposable
{
string ConnectionString
DbConnection Connection
BackendType BackendType
bool IsConnected
Exception LastException
bool Connect(bool reconnect);
bool Connect(string conString, bool reconnect);
bool Disconnect();
}
IBackend
This inherits from IBackendSource
and defines data manipulation operations like accessing data, executing queries etc.
public interface IBackend : IBackendSource
{
DataTable GetData(string sql);
DataTable GetData(DbCommand command);
void GetData(string sql, ref DataTable dt);
void GetData(DbCommand command, ref DataTable dt);
int ExecuteNonQuery(string sqlCommand, CommandType cmdType);
int ExecuteNonQuery(DbCommand command);
int ExecuteNonQuery(string sql, CommandType cmdType,
ParameterEngine param);
object GetSingleValue(DbCommand command);
object GetSingleValue(string sql, CommandType cmdType);
object GetSingleValue(string sql, CommandType cmdType,
ParameterEngine param);
DbCommand GenerateCommand(string sql, CommandType cmdType,
ParameterEngine param);
DbParameter CreateParameter();
DbTransaction BeginTransaction();
DbTransaction BeginTransaction(IsolationLevel isolationLevel);
void CommitTransaction();
void RollbackTransaction();
}
DataAccess
This is an abstract class that inherits from the IBackend
interface and provides concrete implementation of a certain functionality specified in IBackend
. This class acts as a base class for deriving and implementing concrete data access classes for SQL Server, Oracle, ODBC, and OLEDB type of components defined in the ADO.NET library. This class implements and handles functionality like connection management, DB command generation, transaction management etc., so that the deriving classes need not have to worry about implementing these. They have to only override the abstract functionalities (derived from IBackend
) from this class and provide the source specific implementation.
This class also defines a constant ERROR_RESULT
. Currently, in the derived classes, the exceptions from ExecuteNonQuery
operations are caught inside derived classes and update the LastException
property. At that time, this ERROR_RESULT
constant is returned as the return value of ExecuteNonQuery
operations. In this case, you have to check the LastException
property of the IBackendSource
to identify the generated exception. Otherwise, the driver specific return value is returned back.
public abstract class DataAccess : IBackend
{
public const int ERROR_RESULT = int.MaxValue;
protected abstract void createConnection();
protected abstract DbCommand getCommand(ref string sqlCommand,
CommandType cmdType);
public abstract DataTable GetData(string sql);
public abstract DataTable GetData(DbCommand command);
public abstract void GetData(string sql, ref DataTable dt);
public abstract void GetData(DbCommand command, ref DataTable dt);
public abstract int ExecuteNonQuery(string sqlCommand, CommandType cmdType);
public abstract int ExecuteNonQuery(DbCommand command);
public abstract int ExecuteNonQuery(string sql,
CommandType cmdType, ParameterEngine param);
public abstract object GetSingleValue(DbCommand command);
public abstract object GetSingleValue(string sql, CommandType cmdType);
public abstract object GetSingleValue(string sql,
CommandType cmdType, ParameterEngine param);
public abstract DbCommand GenerateCommand(string sql,
CommandType cmdType, ParameterEngine param);
public abstract DbParameter CreateParameter();
}
public class SQLServerDB : DataAccess
{
}
public class OracleDB : DataAccess
{
}
public class OdbcAccess : DataAccess
{
}
public class OleDbAccess : DataAccess
{
}
ParameterEngine
ParameterEngine
is a new concept introduced in this DAL component which provides transparent and easy manipulation of database parameters. Irrespective of the type of backend, users can manage the input and output parameters that can be passed to Stored Procedures (this parameter engine supports only SQL, Oracle, ODBC, and OLEDB type of backends). It can be used for the retrieval of output parameters after query execution. Once the instance of the parameter engine is created and filled up with the parameters, it can be used to generate database commands and then execute the queries.
public sealed class ParameterEngine
{
public bool Add(string name, object value)
public bool Add(string name, DbType dataTypeEnum, int size, object value)
public bool Add(string name, DbType dataTypeEnum, int size,
string sourceColumn, object value)
public bool Add(string name, DbType dataTypeEnum, int size,
ParameterDirection direction, string sourceColumn,
DataRowVersion sourceVersion, object value)
public bool AddOutputParameter(string name)
public bool AddOutputParameter(string name, int size)
public bool AddOutputParameter(string name, DbType dataType, int size)
public object RetrieveOutputParameterValue(string parameterName)
}
B. Utilities
Some utilities are incorporated in the component. Some are still under research. Presently, two static utility classes named SQLInstancesFinder
and ODBCDataSourcesFinder
are functional.
SQLInstancesFinder
This class provides static functionalities to query SQL Server instances available over a network. Each SQL Server instance is represented using the SQLInstancesFinder.SQLServerInstance
class, which contains the name and version information of that instance. Additional functionalities are there which retrieve the name of databases in a particular server using Windows/SQL authentication.
public static class SQLInstancesFinder
{
public static List<SQLServerInstance> GetNetworkSQLServerInstances()
public static List<string> GetDatabasesFromServer(string server)
public static List<string> GetDatabasesFromServer(string server,
string useName, string password)
}
ODBCDataSourcesFinder
This class provides the static functionalities to query the ODBC data sources (System/User/File) configured in a computer. Each source is represented using the ODBCDataSourcesFinder.ODBCDataSource
class, which contains the name and driver information of that data source.
public static class ODBCDataSourcesFinder
{
public static List<ODBCDataSource> GetAllDSNs()
public static List<ODBCDataSource> GetSystemDSNs()
public static List<ODBCDataSource> GetUserDSNs()
public static List<ODBCDataSource> GetFileDSNs()
}
C. User Controls
SQLConnectionSetupControl
This is a Windows user control similar to the Add Connection dialog in the Server Explorer. This can be used to connect interactively to a server in the network, and connect to a database, and finally generate the connection string.
public partial class SQLConnectionSetupControl : UserControl
{
[DefaultValue(typeof(SQLConnectionSetupControl.LogonMode),
"Windows")]
public LogonMode AuthenticationMode
public string ConnectionString
public string ServerName
public string DatabaseName
public string UserName
public string Password
[DefaultValue(-1)]
public int TimeOut
[DefaultValue(false)]
public bool AutoSizeParentForm
[DefaultValue(false)]
public bool CloseParentFormOnOKCancel
[DefaultValue(false)]
public bool UpdateParentFormStatus
[DefaultValue(true)]
public bool ShowTestButton
[DefaultValue("")]
public string ParentFormStatusMessage
[DefaultValue("&OK")]
public string OKButtonText
[DefaultValue("&Cancel")]
public string CancelButtonText
public bool CheckConnection()
}
Using the Component
The below code segments illustrates the usage of the component. You can find the same code in FormUsage.cs in the TestApp project. The test project contains another form that demonstrates the usage of SQLConnectionSetupControl
.
DataAccess da = null;
string conStr =
"Data Source=localhost;Initial Catalog=MyDB;User ID=sa;Password=pwd";
private static DataAccess _CreateDataSource(BackendType type)
{
DataAccess ret = null;
switch (type)
{
case BackendType.SQL: ret = new SQLServerDB(); break;
case BackendType.Oracle: ret = new OracleDB(); break;
case BackendType.Odbc: ret = new OdbcAccess(); break;
case BackendType.OleDb: ret = new OleDbAccess(); break;
}
return ret;
}
private void Init()
{
this.da = _CreateDataSource(BackendType.SQL);
}
private void Connect()
{
this.da.ConnectionString = conStr;
this.da.Connect(false);
this.da.Connect(conStr, true);
}
private void Disconenct()
{
this.da.Disconnect();
}
private void GetData()
{
DataTable dt1 = this.da.GetData("SELECT * FROM Users");
DataTable dt2 = new DataTable();
this.da.GetData("SELECT * FROM Users", ref dt2);
DbCommand cmd1 = this._GenCommand();
DataTable dt3 = this.da.GetData(cmd1);
DbCommand cmd2 = this._GenCommand();
DataTable dt4 = new DataTable();
this.da.GetData(cmd2, ref dt4);
}
private void GetSingleValue()
{
DbCommand cmd1 = this._GenCommand();
object o1 = this.da.GetSingleValue(cmd1);
object o2 = this.da.GetSingleValue("Query", CommandType.Text);
object o3 = this.da.GetSingleValue("Procedure",
CommandType.StoredProcedure);
ParameterEngine pe = this._CreateParamEngine();
object o4 = this.da.GetSingleValue("Procedure",
CommandType.StoredProcedure, pe);
}
private void ExecuteNonQuery()
{
int result = 0;
DbCommand cmd1 = this._GenCommand();
result = this.da.ExecuteNonQuery(cmd1);
result = this.da.ExecuteNonQuery("Query", CommandType.Text);
result = this.da.ExecuteNonQuery("Procedure",
CommandType.StoredProcedure);
ParameterEngine pe = this._CreateParamEngine();
result = this.da.ExecuteNonQuery("Procedure",
CommandType.StoredProcedure, pe);
if (result == DataAccess.ERROR_RESULT)
{
throw this.da.LastException;
}
}
private DbCommand _GenCommand()
{
DbCommand cmd1 = this.da.GenerateCommand(
"SELECT * FROM Users", CommandType.Text, null);
ParameterEngine pe = this._CreateParamEngine();
DbCommand cmd2 = this.da.GenerateCommand("spMyProcedure",
CommandType.StoredProcedure, pe);
return null;
}
private ParameterEngine _CreateParamEngine()
{
ParameterEngine pe = ParameterEngine.New(this.da);
pe.Add("param1", 1);
pe.Add("param1", DateTime.Now);
pe.Add("param1", "Hello");
pe.AddOutputParameter("OutParam1");
pe.AddOutputParameter("OutParam2", DbType.Int32, 4);
return pe;
}
private object _RetrieveOutputValue(ParameterEngine pe,
string paramName)
{
object ret = pe.RetrieveOutputParameterValue(paramName);
return ret;
}
private void Transaction()
{
DbTransaction transaction = this.da.BeginTransaction();
this.da.CommitTransaction();
this.da.RollbackTransaction();
}
Future
I am looking on bringing up new features to this component. You can see some other classes in the code, which are still under construction. The next feature will probably be a schema retrieval class using which the schema of tables, procedures, field names etc., can be retrieved from a data source. I appreciate your suggestions.
History
- 22-Dec-2009: Posted the article.