理想的开端
古人说得好:"道虽迩,不行不至;事虽小,不为不成。"理想之所以美好,不仅仅在于它的最终实现,而且体现在其实现过程中,体现在实现理想的平凡劳动中。列宁说:"要成就一件大事业,必须从一点一滴做起。""少说些漂亮话,多做些日常平凡的事情。"伟大来自平凡,任何伟大成就,都是由无数具体、平凡的工作积累、发展起来的。追求理想是一个艰苦奋斗的过程,这是由理想的长期性、艰巨性和曲折性决定的。为了实现自己的理想做好准备,我们需要为自己打下良好的基础。
在三层架构设计中,数据库访问层的设计就是为软件提供的一个稳定、可靠的基石。
数据库访问技术概述
在软件开发中,数据库编程是非常常见的工作。从数据库技术诞生到现在,已经有数十年的历史。从最初的磁盘文件(也称文件数据库)开始,数据库的发展非常迅猛。目前市场上比较常见的关系型数据有SQL Server、Oracle、DB2、Informix、Sybase、MySQL、Access等。各种数据都自己独特的优势和特点。最初,不同的数据库厂商提供了不同的访问接口,开发人员必须熟悉根据不同数据采用不同的访问技术。后来,数据库中间件的出现为开发人员带来新的开发体验,如ODBC、OLEDB、ADO等。
现在,我们简单看一下主流数据访问技术:
1) JDBC(Java Database Connectivity:Java 数据库连接),JDBC是JAVA环境下的数据访问技术,有四种类型:
·JDBC-ODBC桥。由SUN公司提供的,是jdk提供的的标准api. 这种类型的驱动实际是把所有jdbc的调用传递给odbc
·本地API驱动。把JDBC调用转变为数据库的标准调用再去访问数据库。
·网络协议驱动。使用纯 JAVA 客户机,并使用独立于数据库的协议与中间件服务器通信,然后中间件服务器将客户机请求传给数据源。
·本地协议驱动。用纯JAVA编写,实现针对特定数据源的网络协议,客户机直接连接至数据源。
2 ) 微软相关数据访问技术
·ODBC(Open Database Connectivity):是第一个使用SQL访问各种关系数据库的数据访问技术。使用ODBC,应用程序能够可以操纵不同类型的数据库,而开发人员需要根据不同数据访问不同的ODBC 驱动就可以。
·DAO(Data Access Objects):DAO是微软提供给 Visual Basic 开发人员的一种简单的数据访问方法,用于操纵 Access 数据库。
·RDO(Remote Data Object):在使用 DAO 访问关系型数据库的时候,JET 引擎需要在DAO和ODBC 之间进行转化,导致了性能的下降。
·OLE DB:是为各种关系型数据提供的一种应用和数据源之间的无缝连接的基于COM的数据库访问技术。
·ADO:基于 OLE DB 之上,ADO 更简单、更高级,同时消除了 OLE DB 的多种弊端,是微软技术发展的趋势。
·ADO.NET:微软在.NET 框架中提出的全新的数据访问模型。
ADO.NET访问数据库
ADO.NET2.0访问数据库可以采用以下方式:
1) System.Data.Odbc,通过ODBC数据源访问数据库,可以访问各种关系型数据库,但是需要配置ODBC数据源;
2) System.Data.OleDb,通过不同数据库驱动程序访问,可以访问各种关系型数据库;
3) System.Data.SqlClient,MS为专门访问SQL Server提供;
4) System.Data.OracleClient,MS为专门访问Oracle提供。
数据库访问层设计
在SmartPublisher软件需求中,要求本软件对不同关系型数据库的支持能力。而ADO.NET针对不同的数据有不同的访问方式。根据面向对象程序设计的思想,我们应该把不同数据库访问抽象成独立的访问类。我们先考虑支持SQL Server和Oracle。实现如下:
public class SqlDBOperator


{
//操作SQL Server数据库
}
public class OracleDBOperator


{
//操作Oracle数据库
}
在软件开发过程中,应用程序对数据库的操作过程不外乎为:先连接数据,然后数据表操作,最后关闭数据库,即其基本操作是相同的。所以,我们可以把对数据库的操作抽象(抽出象的部分即为抽象)出来,即定义一个父类,此处为抽象类,并在该类中提供数据访问的常用方法。如下:
public abstract class DBOperator


{
//连接数据库
//操作数据表
//关闭数据库
}
然后根据不同的数据库由SqlDBOperator、OracleDBOperator等具体访问类override父类DBOperator中的方法。为了使数据访问支持事务处理,这里还提供事务处理的有关方法。下面请看具体实现代码。

DBOperator抽象类
using System;
using System.Data;

namespace PublisherLib.DbAccess


{
public abstract class DBOperator

{
//执行写表的SQL语句,如INSERT、UPDATE、DELETE
public abstract void Execute(string sql, string[] strParams, object[] strValues);
//批次执行写表的SQL语句,如INSERT、UPDATE、DELETE
public abstract void ExecuteTransaction(string[] sqlList, string[][] strParams, object[][] strValues);
//执行读表的SQL语句,如SELECT,返回单个对象
public abstract object ExecuteForObject(string sql);//执行
//执行读表的SQL语句,如SELECT,返回DataSet
public abstract DataSet ExecuteForDataSet(string sql);

//执行写表的存储过程
public abstract void ExecuteProceduce(string sql, string[] strParams, object[] strValues);
//执行读表的存储过程,返回DataSet
public abstract DataSet ExecuteProceduceForDataSet(string sql, string[] strParams, object[] strValues);
}
}

SQL Server访问类
using System;
using System.Data;
using System.Data.SqlClient;

namespace PublisherLib.DbAccess


{
public class SqlDBOperator:DBOperator

{
private string strConnection;
public SqlDBOperator(string strConnection)

{
this.strConnection = strConnection;
}
public override void Execute(string sql, string[] strParams, object[] strValues)

{
using (SqlConnection conn = new SqlConnection(strConnection))

{
using (SqlCommand cmd = new SqlCommand(sql, conn))

{
try

{
conn.Open();

if (strParams != null)

{
for (int i = 0; i < strParams.Length; i++)
cmd.Parameters.AddWithValue(DBConfig.ParaSign + strParams[i], strValues[i]);
}

cmd.ExecuteNonQuery();
}
catch (SqlException ex)

{
conn.Close();
throw new Exception(ex.Message);
}
}
}
}

public override void ExecuteTransaction(string[] sqlList, string[][] strParams, object[][] strValues)

{
using (SqlConnection conn = new SqlConnection(strConnection))

{
conn.Open();
SqlCommand cmd = new SqlCommand();
cmd.Connection = conn;
SqlTransaction transaction = conn.BeginTransaction();
cmd.Transaction = transaction;

try

{
for (int i = 0; i < sqlList.Length; i++)

{
cmd.CommandText = sqlList[i];
if (strParams != null)

{
for (int j = 0; j < strValues[i].Length; j++)

{
cmd.Parameters.AddWithValue(DBConfig.ParaSign + strParams[i][j], strValues[i][j]);
}
}
cmd.ExecuteNonQuery();
}

transaction.Commit();
}
catch (SqlException ex)

{
transaction.Rollback();

conn.Close();
throw new Exception(ex.Message);
}
}
}

public override object ExecuteForObject(string sql)

{
using (SqlConnection conn = new SqlConnection(strConnection))

{
using (SqlCommand cmd = new SqlCommand(sql, conn))

{
try

{
conn.Open();

object obj = cmd.ExecuteScalar();
if (Object.Equals(obj, null) || Object.Equals(obj, System.DBNull.Value))
return null;
else
return obj;
}
catch (SqlException ex)

{
conn.Close();
throw new Exception(ex.Message);
}
}
}
}

public override DataSet ExecuteForDataSet(string sql)

{
using (SqlConnection conn = new SqlConnection(strConnection))

{
using (SqlDataAdapter data = new SqlDataAdapter(sql,conn))

{
DataSet ds = new DataSet();
try

{
data.Fill(ds, "ds");
}
catch (SqlException ex)

{
throw new Exception(ex.Message);
}
return ds;
}
}
}

public override void ExecuteProceduce(string sql, string[] strParams, object[] strValues)

{
using (SqlConnection conn = new SqlConnection(strConnection))

{
using (SqlCommand cmd = new SqlCommand(sql, conn))

{
try

{
conn.Open();
cmd.CommandType = CommandType.StoredProcedure;
if (strParams != null)

{
for (int i = 0; i < strParams.Length; i++)
cmd.Parameters.AddWithValue(DBConfig.ParaSign + strParams[i], strValues[i]);
}

cmd.ExecuteNonQuery();
}
catch (SqlException ex)

{
conn.Close();
throw new Exception(ex.Message);
}
}
}
}

public override DataSet ExecuteProceduceForDataSet(string sql, string[] strParams, object[] strValues)

{
using (SqlConnection conn = new SqlConnection(strConnection))

{
using (SqlCommand cmd = new SqlCommand(sql, conn))

{
try

{
conn.Open();
cmd.CommandType = CommandType.StoredProcedure;
if (strParams != null)

{
for (int i = 0; i < strParams.Length; i++)
cmd.Parameters.AddWithValue(DBConfig.ParaSign + strParams[i], strValues[i]);
}
SqlDataAdapter data = new SqlDataAdapter();
data.SelectCommand = cmd;
DataSet ds = new DataSet();
data.Fill(ds);

return ds;
}
catch (SqlException ex)

{
conn.Close();
throw new Exception(ex.Message);
}
}
}
}
}
}

Oracle访问类
using System;
using System.Data;
using System.Data.OracleClient;

namespace PublisherLib.DbAccess


{
public class OracleDBOperator : DBOperator

{
private string strConnection;
public OracleDBOperator(string strConnection)

{
this.strConnection = strConnection;
}
public override void Execute(string sql, string[] strParams, object[] strValues)

{
using (OracleConnection conn = new OracleConnection(strConnection))

{
using (OracleCommand cmd = new OracleCommand(sql, conn))

{
try

{
conn.Open();

if (strParams != null)

{
for (int i = 0; i < strParams.Length; i++)
cmd.Parameters.AddWithValue(DBConfig.ParaSign + strParams[i], strValues[i]);
}

cmd.ExecuteNonQuery();
}
catch (OracleException ex)

{
conn.Close();
throw new Exception(ex.Message);
}
}
}
}

public override void ExecuteTransaction(string[] sqlList, string[][] strParams, object[][] strValues)

{
using (OracleConnection conn = new OracleConnection(strConnection))

{
conn.Open();
OracleCommand cmd = new OracleCommand();
cmd.Connection = conn;
OracleTransaction transaction = conn.BeginTransaction();
cmd.Transaction = transaction;

try

{
for (int i = 0; i < sqlList.Length; i++)

{
cmd.CommandText = sqlList[i];
if (strParams != null)

{
for (int j = 0; j < strValues[i].Length; j++)

{
cmd.Parameters.AddWithValue(DBConfig.ParaSign + strParams[i][j], strValues[i][j]);
}
}
cmd.ExecuteNonQuery();
}

transaction.Commit();
}
catch (OracleException ex)

{
transaction.Rollback();

conn.Close();
throw new Exception(ex.Message);
}
}
}

public override object ExecuteForObject(string sql)

{
using (OracleConnection conn = new OracleConnection(strConnection))

{
using (OracleCommand cmd = new OracleCommand(sql, conn))

{
try

{
conn.Open();

object obj = cmd.ExecuteScalar();
if (Object.Equals(obj, null) || Object.Equals(obj, System.DBNull.Value))
return null;
else
return obj;
}
catch (OracleException ex)

{
conn.Close();
throw new Exception(ex.Message);
}
}
}
}

public override DataSet ExecuteForDataSet(string sql)

{
using (OracleConnection conn = new OracleConnection(strConnection))

{
using (OracleDataAdapter data = new OracleDataAdapter(sql, conn))

{
DataSet ds = new DataSet();
try

{
data.Fill(ds, "ds");
}
catch (OracleException ex)

{
throw new Exception(ex.Message);
}
return ds;
}
}
}

public override void ExecuteProceduce(string sql, string[] strParams, object[] strValues)

{
using (OracleConnection conn = new OracleConnection(strConnection))

{
using (OracleCommand cmd = new OracleCommand(sql, conn))

{
try

{
conn.Open();
cmd.CommandType = CommandType.StoredProcedure;
if (strParams != null)

{
for (int i = 0; i < strParams.Length; i++)
cmd.Parameters.AddWithValue(DBConfig.ParaSign + strParams[i], strValues[i]);
}

cmd.ExecuteNonQuery();
}
catch (OracleException ex)

{
conn.Close();
throw new Exception(ex.Message);
}
}
}
}

public override DataSet ExecuteProceduceForDataSet(string sql, string[] strParams, object[] strValues)

{
using (OracleConnection conn = new OracleConnection(strConnection))

{
using (OracleCommand cmd = new OracleCommand(sql, conn))

{
try

{
conn.Open();
cmd.CommandType = CommandType.StoredProcedure;
if (strParams != null)

{
for (int i = 0; i < strParams.Length; i++)
cmd.Parameters.AddWithValue(DBConfig.ParaSign + strParams[i], strValues[i]);
}
OracleDataAdapter data = new OracleDataAdapter();
data.SelectCommand = cmd;
DataSet ds = new DataSet();
data.Fill(ds);

return ds;
}
catch (OracleException ex)

{
conn.Close();
throw new Exception(ex.Message);
}
}
}
}
}
}
DBOperator的引入,通过类的继承机制,使两种数据库访问彻底分离。如果增加其他数据库如MySQL的访问,只需要增加OleDBOperator访问类,并且继承DBOperator类,不需要对上述代码进行修改(开放-封闭原则)。

其他数据库访问类
using System;
using System.Data;
using System.Data.OleDb;

namespace PublisherLib.DbAccess


{
public class OleDbDBOperator : DBOperator

{
private string strConnection;
public OleDbDBOperator(string strConnection)

{
this.strConnection = strConnection;
}
public override void Execute(string OleDb, string[] strParams, object[] strValues)

{
using (OleDbConnection conn = new OleDbConnection(strConnection))

{
using (OleDbCommand cmd = new OleDbCommand(OleDb, conn))

{
try

{
conn.Open();

if (strParams != null)

{
for (int i = 0; i < strParams.Length; i++)
cmd.Parameters.AddWithValue(DBConfig.ParaSign + strParams[i], strValues[i]);
}

cmd.ExecuteNonQuery();
}
catch (OleDbException ex)

{
conn.Close();
throw new Exception(ex.Message);
}
}
}
}

public override void ExecuteTransaction(string[] sqlList, string[][] strParams, object[][] strValues)

{
using (OleDbConnection conn = new OleDbConnection(strConnection))

{
conn.Open();
OleDbCommand cmd = new OleDbCommand();
cmd.Connection = conn;
OleDbTransaction transaction = conn.BeginTransaction();
cmd.Transaction = transaction;

try

{
for (int i = 0; i < sqlList.Length; i++)

{
cmd.CommandText = sqlList[i];
if (strParams != null)

{
for (int j = 0; j < strValues[i].Length; j++)

{
cmd.Parameters.AddWithValue(DBConfig.ParaSign + strParams[i][j], strValues[i][j]);
}
}
cmd.ExecuteNonQuery();
}

transaction.Commit();
}
catch (OleDbException ex)

{
transaction.Rollback();

conn.Close();
throw new Exception(ex.Message);
}
}
}

public override object ExecuteForObject(string OleDb)

{
using (OleDbConnection conn = new OleDbConnection(strConnection))

{
using (OleDbCommand cmd = new OleDbCommand(OleDb, conn))

{
try

{
conn.Open();

object obj = cmd.ExecuteScalar();
if (Object.Equals(obj, null) || Object.Equals(obj, System.DBNull.Value))
return null;
else
return obj;
}
catch (OleDbException ex)

{
conn.Close();
throw new Exception(ex.Message);
}
}
}
}

public override DataSet ExecuteForDataSet(string OleDb)

{
using (OleDbConnection conn = new OleDbConnection(strConnection))

{
using (OleDbDataAdapter data = new OleDbDataAdapter(OleDb, conn))

{
DataSet ds = new DataSet();
try

{
data.Fill(ds, "ds");
}
catch (OleDbException ex)

{
throw new Exception(ex.Message);
}
return ds;
}
}
}

public override void ExecuteProceduce(string OleDb, string[] strParams, object[] strValues)

{
using (OleDbConnection conn = new OleDbConnection(strConnection))

{
using (OleDbCommand cmd = new OleDbCommand(OleDb, conn))

{
try

{
conn.Open();
cmd.CommandType = CommandType.StoredProcedure;
if (strParams != null)

{
for (int i = 0; i < strParams.Length; i++)
cmd.Parameters.AddWithValue(DBConfig.ParaSign + strParams[i], strValues[i]);
}

cmd.ExecuteNonQuery();
}
catch (OleDbException ex)

{
conn.Close();
throw new Exception(ex.Message);
}
}
}
}

public override DataSet ExecuteProceduceForDataSet(string OleDb, string[] strParams, object[] strValues)

{
using (OleDbConnection conn = new OleDbConnection(strConnection))

{
using (OleDbCommand cmd = new OleDbCommand(OleDb, conn))

{
try

{
conn.Open();
cmd.CommandType = CommandType.StoredProcedure;
if (strParams != null)

{
for (int i = 0; i < strParams.Length; i++)
cmd.Parameters.AddWithValue(DBConfig.ParaSign + strParams[i], strValues[i]);
}
OleDbDataAdapter data = new OleDbDataAdapter();
data.SelectCommand = cmd;
DataSet ds = new DataSet();
data.Fill(ds);

return ds;
}
catch (OleDbException ex)

{
conn.Close();
throw new Exception(ex.Message);
}
}
}
}
}
}
你可能已经发现了一个问题,我们究竟用三个访问类中的哪个呢?其实只要创建类DBConfig,并定义静态量DBType,并根据其值判断。所以调用时,实现如下:
DBOperator db;
if (DBConfig.DBType=="SQLSERVER")
{
db=new SqlDBOperator("SQLSERVER连接字串");
}
else if (DBConfig.DBType=="ORACLE")
{
db=new OracleDBOperator("ORACLE连接字串");
}
else
{
db=new OleDBOperator("其他连接字串");
}
看到这个代码,肯定很多人都会感冒。嵌套的if ...else...语句确实感到一个"晕"字。当然我们可以IF的同胞兄弟switch。但是无论如何都叫人感觉不爽。根治嵌套的if ...else...语句和switch等多分支之"病"的最有效的秘方就是使用工厂方法模式,通过工厂类与抽象类的关联,并且采用"依赖注入"和"反射",通过系统外的配置决定实例化哪个访问类。改进一下DBConfig类。

DBConfig类
using System;

namespace PublisherLib.DbAccess


{
public class DBConfig

{
public static string ConnectionString

{

get
{ return "server=(local);user id=sa;password=pass;database=publisher"; }
}
public static string Dll

{

get
{ return @"PublisherDB.dll"; }
}
public static string ClassName

{

get
{ return "PublisherDB.SqlDBOperator"; }
}
public static string ParaSign

{

get
{ return "@"; }
}
}
}

DBOperator工厂类
using System;
using System.Data;
using System.Reflection;

namespace PublisherLib.DbAccess


{
public class DBOperatorFactory

{
public static DBOperator CreateInstance()

{

object[] args =
{ DBConfig.ConnectionString };

return (DBOperator)Assembly.Load(DBConfig.Dll).CreateInstance(DBConfig.ClassName, false,
BindingFlags.Default, null, args, null, null);
}
}
}
调用代码如下:
DBOperator db=DBOperatorFactory.CreateInstance();
我们来看看DBOperator、SqlDBOperator、OracleDBOperator、OleDbDBOperator与DBOperatorFactory直接的关系。

SmartPublisher技术架构

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· [AI/GPT/综述] AI Agent的设计模式综述