上一篇打造自己的数据访问层(一)中,我们已了解了.NET对数据库操作的基本原理,并就Ado.net对象的使用提出了几点疑问:
1、如何由系统来判断数据库型。
2、如何消除这些重复代码。
而上篇中也提出了一种解决思路,对ADO.NET对象进行封装,具体应该如何实施?
1、需要一个对象,该对象用于建立内存表与物理表的之间映射关系,解决数据查询、更新操作,形成了数据映射对象,定义为DataMapping。
2、每一个映射对象只与一张物理建立映射关系,如果有多个这样的对象同时操作,如何解决?这时就需要另一个对象,用于添加映射对象集合,打包映射对象操作,形成了数据执行者,定义为DataExecutor。
想想看,只需要这两个基本对象,就可以形成简单的数据访问层了。
先实现DataMapping,它应具备如下功能。
1、需要知道物理表的信息,表名、主键集、字段集。
2、需要知道映射的是什么类型的数据库,MSSql数据库、Oracle数据库、MySql数据库或者其他类型的数据库。
3、可查询数据,即填充内存表。
4、可更新数据,并且可设置更新操作方式。
5、可加入到事务中去。
根据上述功能,可初步设计出的DataMapping类:
public class DataMapping
{
public DataMapping()
{ }
/// <summary>
/// 填充数据集
/// </summary>
public void Fill()
{
}
/// <summary>
/// 设置更新命令
/// </summary>
public void SetCommands()
{
}
/// <summary>
/// 设置数据提交事务
/// </summary>
public void SetTransaction()
{
}
/// <summary>
/// 提交数据
/// </summary>
public bool Update()
{
}
/// <summary>
/// 更新列名
/// </summary>
public string Columns
{
get
{
return columns;
}
set
{
columns = value;
}
}
private string columns = "";
/// <summary>
/// 主键名
/// </summary>
public string KeyColumns
{
get
{
return keyColumns;
}
set
{
keyColumns = value;
}
}
private string keyColumns = "";
/// <summary>
/// 表名
/// </summary>
public string TableName
{
get
{
return tableName;
}
set
{
tableName = value;
}
}
private string tableName = "";
}
再来实现DataExecutor类,它应具备的功能:
1、应该知道执行什么类型的数据库操作。
2、可以添加映射对象。
3、可以进行数据提交。
如何来知道执行的数据库类型,我们可以定义具体的执行者,比如MSSql执行者、Oracle执行者、MySql执行者。
可以初步设计出DataExecutor类
public abstract class DataExecutor
{
private IList<DataMapping> lisDataMappings = new List<DataMapping>();
/// <summary>
/// 添加数据映射对象
/// </summary>
public void AddDataMapping(DataMapping map)
{
}
/// <summary>
/// 更新数据
/// </summary>
public bool Update()
{
}
}
public class MSSqlExecutor : DataExecutor
{
}
public class OracleExecutor : DataExecutor
{
}
public class MySqlExecutor : DataExecutor
{
}
准备就绪,开始行具体设计。
先从DataMapping的Fill方法入手,看看它是如何查询数据的。
public void Fill(string sqlText, string tableName, DataSet ds)
{
IDbConnection conn = 具体的数据连接对象;
IDbDataAdapter dataAdapter = 具体的数据适配对象;
IDbCommand cmd = 具体的命令对象;
cmd.Connection = conn;
cmd.CommandText = sqlText;
dataAdapter.SelectCommand = cmd;
((DbDataAdapter)dataAdapter).Fill(ds, tableName);
}
问题出来了,这里出现了具体的对象,如何得到这些对象?
前面我们设计了MSSqlExecutor类,它已经知道具体的数据库类型,所以它也应该知道进行数据操作的具体的对象,DataMapping类是否可以引用该它,从而通过它来获取数据操作对象,因此,可以MSSqlExecutor类及DataMapping类进行修改,使DataMapping对MSSqlExecutor类产生依赖关系;这只是对MSSql数据库进行操作,现要改变数据库对象为Oracle了,DataMapping类应该也需要对OracleExecutor类产生依赖关系。
因此,这里可以设计一个接口,用于获取具体对象:
/// <summary>
/// 映射执行接口
/// </summary>
public interface IMappingExecute
{
/// <summary>
/// 获取连接对象
/// </summary>
IDbConnection GetConn();
/// <summary>
/// 获取数据适配器
/// </summary>
IDbDataAdapter GetDataAdapter();
/// <summary>
/// 获取命令对象
/// </summary>
IDbCommand GetCommand();
/// <summary>
/// 获取命令参数
/// </summary>
IDbDataParameter GetDataParameter(string col);
/// <summary>
/// 获取命令参数
/// 数据库之间的命令参类是不一样的
/// MMSql是“@” + 列名,Oracle是 “:” + 列名,MySql是 “?” + 列名
/// </summary>
string GetSourceColumn(string col);
}
改造后的MSSqlExecutor类为:
public class MSSqlExecutor : DataExecutor, IMappingExecute
{
}
改造后的DataMapping类为:
public class DataMapping
{
private IDbConnection conn = null;
private IDbDataAdapter dataAdapter = null;
/// <summary>
/// 映射执行对象
/// </summary>
public IMappingExecute ExecuteObject
{
set
{
executeObj = value;
conn = executeObj.GetConn();
}
}
private IMappingExecute executeObj;
/// <summary>
/// 填充数据集
/// 参数:查询语句
/// 参数:内存表名
/// </summary>
public void Fill(string sqlText, string tableName, DataSet ds)
{
dataAdapter = executeObj.GetDataAdapter();
IDbCommand cmd = executeObj.GetCommand();
cmd.Connection = conn;
cmd.CommandText = sqlText;
dataAdapter.SelectCommand = cmd;
((DbDataAdapter)dataAdapter).Fill(ds, tableName);
}
}
到此为止,查询功能算是完成了,接下来该实现更新功能了,从SetCommands入手,以新增操作为例:
/// <summary>
/// 设置更新命令
/// </summary>
public void SetCommands(DataCommandType commandType, DataSet ds)
{
if ((commandType & DataCommandType.Insert) == DataCommandType.Insert)
{
CreateInsertCommand(ds);
}
if ((commandType & DataCommandType.Update) == DataCommandType.Update)
{
CreateUpdateCommand(ds);
}
if ((commandType & DataCommandType.Delete) == DataCommandType.Delete)
{
CreateDeleteCommand(ds);
}
}
/// <summary>
/// 生成新增命令及SQL语句
/// </summary>
private void CreateInsertCommand(DataSet ds)
{
IList<string> lisColumns = GetColumns(ds);
StringBuilder sbCol = new StringBuilder();
StringBuilder sbVal = new StringBuilder();
foreach (string col in lisColumns)
{
sbCol.AppendFormat(", {0}", col);
sbVal.AppendFormat(", {0}", executeObj.GetSourceColumn(col));
}
sbCol.Remove(0, 2);
sbVal.Remove(0, 2);
string sqlText = string.Format("INSERT INTO {0} ({1}) VALUES ({2})", tableName, sbCol.ToString(), sbVal.ToString());
IDbCommand cmd = executeObj.GetCommand();
cmd.Connection = conn;
cmd.CommandText = sqlText;
SetCommandParams(cmd, lisColumns);
dataAdapter.InsertCommand = cmd;
}
/// <summary>
/// 获取列字段集
/// </summary>
private IList<string> GetColumns(DataSet ds)
{
IList<string> lisColumns = new List<string>();
if (columns != "*")
{
string[] sltCol = columns.Split(',');
foreach (string col in sltCol)
{
lisColumns.Add(col.Trim());
}
}
else
{
DataTable dt = ds.Tables[tableName];
foreach (DataColumn dc in dt.Columns)
{
lisColumns.Add(dc.ColumnName);
}
}
return lisColumns;
}
更新操作非常简单,就是在上一篇的数据操作原理的基础上动态生成了查询语句及参数绑定,不多做解释。
其中DataCommandType为自定义枚举类型:
/// <summary>
/// 数据操作命令类型
/// </summary>
public enum DataCommandType
{
/// <summary>
/// 新增
/// </summary>
Insert = 1,
/// <summary>
/// 修改
/// </summary>
Update = 2,
/// <summary>
/// 删除
/// </summary>
Delete = 4
}
更新完后进行数据提交:
/// <summary>
/// 提交数据
/// </summary>
public bool Update(DataSet ds)
{
return ((DbDataAdapter)dataAdapter).Update(ds, tableName) > 0;
}
至此,数据更新操作也已经完成,最后再看看数据执行者是如何进行批量提交。
这里产生的第一个问题是,什么时候数据执行者会人将映射对象加入到集合中来,其中一种方法是在DataMapping设置更新的时候自己加入到集合去。
因此, 映射执行接口得多添加一个方法,用于新增映射对象:
/// <summary>
/// 添加数据映射对象
/// </summary>
void AddDataMapping(DataMapping map);
修改SetCommand方法:
/// <summary>
/// 设置更新命令
/// </summary>
public void SetCommands(DataCommandType commandType, DataSet ds)
{
//设置更新事件时添加映射对象
executeObj.AddDataMapping(this);
}
现在执行者中已经存在了,可以进行最后的数据提交:
/// <summary>
/// 更新数据
/// </summary>
public bool Update(DataSet ds)
{
using (conn)
{
if (conn.State == ConnectionState.Closed)
{
conn.Open();
}
IDbTransaction transaction = conn.BeginTransaction(IsolationLevel.ReadCommitted);
foreach (DataMapping map in lisDataMappings)
{
map.SetTransaction(transaction);
}
try
{
foreach (DataMapping map in lisDataMappings)
{
map.Update(ds);
}
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
throw new System.Exception(ex.Message);
}
}
return true;
}
//DataMapping类设置事务
/// <summary>
/// 设置数据提交事务
/// </summary>
public void SetTransaction(IDbTransaction transaction)
{
if (dataAdapter.InsertCommand != null)
{
dataAdapter.InsertCommand.Transaction = transaction;
}
if (dataAdapter.UpdateCommand != null)
{
dataAdapter.UpdateCommand.Transaction = transaction;
}
if (dataAdapter.DeleteCommand != null)
{
dataAdapter.DeleteCommand.Transaction = transaction;
}
}
到些为止,我们自己的数据访问层功能已基本完成,但是,我们要如何对其进行调用,现在的方式真能方便的让我们进行调用吗?答案是否定的。
暂且至此为止,下一篇我们再进行最后的收尾工作,并最终打造成符合自己需求的数据访问层。