数据访问层的数据库提供者接口和数据操作接口
前几天写了数据访问层的概要设计,有些朋友说讲得太概要。这次我针对两个主要接口进行详细描述IdriverType和IDataSession;这两个接口是组件的核心部份。IdriverType作为数据库类型提供者,IDataSession是数据操作的描述者。
IdriverType数据库提供者接口
这个接口的主要功能是实现组件能处理不同的数据库。在.net下面虽然提供了Oledb用于多数据数据库访问,但.Net还针对不同数据库提供操作类,主要在访问数据库时有性能上的优化,如果用Oledb就发挥不了这种优势。自己在编写数据层的情况如何让数据层能够灵活地用各自的数据库访问类来操作数据库呢?其实我们留意一下.NET的数据访问类的实现就看到.Net是提供一系列的数据访问接口方便我们实现数据库访问类的。我们只要通过这些接口操作就可以把具体访问类的实现隐藏起来,不用考虑细节上的东西;文章中所提到的IdriverType就是采用这种原理。
IDriverType其实就是相应数据操作的接口归纳起来,描述如下:
/// <summary>
/// 数据库类型提供描述接口
/// </summary>
public interface IDriverType
{
/// <summary>
/// 获取数据连接对象
/// </summary>
System.Data.IDbConnection Connection
{
get;
}
/// <summary>
/// 获取数据适配器对象
/// </summary>
System.Data.IDbDataAdapter DataAdapter
{
get;
}
/// <summary>
/// 获取操作命令对象
/// </summary>
System.Data.IDbCommand Command
{
get;
}
/// <summary>
/// 获取命令参数
/// </summary>
/// <returns></returns>
System.Data.IDataParameter GetParameter();
/// <summary>
/// 获取命令参数
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
/// <returns></returns>
System.Data.IDataParameter GetParameter(string name,object value);
/// <summary>
/// 获取命令参数
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
/// <param name="direction"></param>
/// <returns></returns>
System.Data.IDataParameter GetParameter(string name,object value,System.Data.ParameterDirection direction);
/// <summary>
/// 参数开头标识符
/// </summary>
string NamedPrefix { get; }
/// <summary>
/// 格式化参数名
/// </summary>
/// <param name="parametername"></param>
/// <returns></returns>
string FormatNameForParameter(string parametername);
}
以上接口只是把所有数据访问对象相应接口规则结合起来,如果了解这方面的东西应该不难理解(MSDN也有相关描述)。对于NamedPrefix这个属性是标识不同数据参数名的前缀。FormatNameForParameter方法把相应的参数名格式化对应数据库的参数名。
下面我们看下基于这个接口的SqlServer数据库提供者实现的代码:
/// <summary>
/// SqlServer数据设备提供类
/// </summary>
public class SqlDriver:IDriverType
{
/// <summary>
/// 构造SqlServer数据设备对象
/// </summary>
public SqlDriver()
{
//
// TODO: 在此处添加构造函数逻辑
//
}
#region IDriverType 成员
/// <summary>
/// 获取数据连接对象
/// </summary>
public System.Data.IDbConnection Connection
{
get
{
// TODO: 添加 SqlDriver.Connection getter 实现
return new System.Data.SqlClient.SqlConnection();
}
}
/// <summary>
/// 获取数据填充对象
/// </summary>
public System.Data.IDbDataAdapter DataAdapter
{
get
{
// TODO: 添加 SqlDriver.DataAdapter getter 实现
return new System.Data.SqlClient.SqlDataAdapter();
}
}
/// <summary>
/// 获取数据命令处理对象
/// </summary>
public System.Data.IDbCommand Command
{
get
{
// TODO: 添加 SqlDriver.Command getter 实现
return new System.Data.SqlClient.SqlCommand();
}
}
/// <summary>
/// 获取命令参数对象
/// </summary>
/// <returns>System.Data.IDataParameter</returns>
public System.Data.IDataParameter GetParameter()
{
// TODO: 添加 SqlDriver.GetParameter 实现
return new System.Data.SqlClient.SqlParameter();
}
/// <summary>
/// 获取指定名称和值的参数对象
/// </summary>
/// <param name="name">参数名</param>
/// <param name="value">参数值</param>
/// <returns></returns>
System.Data.IDataParameter HFSoft.Data.IDriverType.GetParameter(string name, object value)
{
// TODO: 添加 SqlDriver.HFSoft.Data.IDriverType.GetParameter 实现
return new System.Data.SqlClient.SqlParameter(this.FormatNameForParameter(name),value);
}
/// <summary>
/// 获取指定名称和值的参数对象
/// </summary>
/// <param name="name">参数名</param>
/// <param name="value">参数值</param>
/// <param name="direction">参数类型</param>
/// <returns>System.Data.IDataParameter</returns>
System.Data.IDataParameter HFSoft.Data.IDriverType.GetParameter(string name, object value, System.Data.ParameterDirection direction)
{
// TODO: 添加 SqlDriver.HFSoft.Data.IDriverType.GetParameter 实现
System.Data.SqlClient.SqlParameter paremter = new System.Data.SqlClient.SqlParameter(this.FormatNameForParameter(name),value);
paremter.Direction = direction;
return paremter;
}
/// <summary>
/// 参数开头标识符
/// </summary>
public string NamedPrefix
{
get
{
return "@";
}
}
/// <summary>
/// 获取相关参数的名称
/// </summary>
/// <param name="parametername">参数名称</param>
/// <returns>string</returns>
public string FormatNameForParameter(string parametername)
{
return NamedPrefix +parametername;
}
#endregion
}
从以上代码可以看到这个数据库提供者的描述和实现只是很简单;原理就是通过接口编程来屏蔽模块与模块细节实现,在编写数据访问时就不用考虑针对不同数据库写不同的代码,只针对接口写数据库访问代码就可以了。数据库提供者是可以后期实现,这个接口在设计上越简单越好;毕竟不是所有的数据提供者都是由自己来实现,有可以能是其他开发人员实现,所以尽可能简单。
下面就讲术如何利用IDriverType来编写访问数据的代码。通常我们在实现某一功能时最好是把接口规定下来,这样有一个好处就是可以规范我们编写的代码;其实接口还有一些意想不到的好处,你用多了就会体现出来(这些东西就不详细描述了)。
数据操作接口IDataSession
既然是数据访问,那我们要清楚它需要做些什么。一个数据层首要具备的功能就是处理所有实现IDbCommand的对象和事务处理等;为了操作方便还可以在它基础创建一些简单O/R Mapping的对象操作功能;这些东西都可以根据自己的情况进行制定。
/// <summary>
/// 数据描述接口
/// </summary>
public interface IDataSession:IDisposable
{
/// <summary>
/// 获取或设置数据提供类型
/// </summary>
IDriverType DriverType
{
get;
set;
}
/// <summary>
/// 数据库连接字符串
/// </summary>
string ConnectionString
{
get;
set;
}
/// <summary>
/// 获取数据连接对象
/// </summary>
System.Data.IDbConnection Connection
{
get;
}
/// <summary>
/// 获取当前事务环境
/// </summary>
System.Data.IDbTransaction Transaction
{
get;
}
/// <summary>
/// 执行命令对象,并返回查询对象
/// </summary>
/// <param name="cmd"></param>
/// <returns></returns>
System.Data.IDataReader ExecuteReader(System.Data.IDbCommand cmd);
/// <summary>
/// 执行命令对象,并返回第一条记录第一列的值
/// </summary>
/// <param name="cmd"></param>
/// <returns></returns>
object ExecuteScalar(System.Data.IDbCommand cmd);
/// <summary>
/// 执行命令,并返回影响的行数
/// </summary>
/// <param name="cmd"></param>
/// <returns></returns>
int ExecuteNonQuery(System.Data.IDbCommand cmd);
/// <summary>
/// 执行命令,并返回查询的记录集
/// </summary>
/// <param name="cmd"></param>
/// <returns></returns>
System.Data.DataSet ExecuteDataSet(System.Data.IDbCommand cmd);
/// <summary>
/// 批执行命令对象
/// </summary>
/// <param name="cmd"></param>
void ExcuteCmds(params System.Data.IDbCommand[] cmd);
/// <summary>
/// 打开数据处理环境
/// </summary>
void Open();
/// <summary>
/// 关闭数据处理环境
/// </summary>
void Close();
/// <summary>
/// 打开事务
/// </summary>
void BeginTran();
/// <summary>
/// 提交事务
/// </summary>
void Commit();
/// <summary>
/// 回退事务
/// </summary>
void RoolBack();
/// <summary>
/// 对象映射容器
/// </summary>
MappingContainer Container
{
get;
set;
}
#region object access
/// <summary>
/// 删除指定的实体对象
/// </summary>
/// <param name="entity">实体对象</param>
void Delete(object entity);
/// <summary>
/// 保存指定的实体对象
/// </summary>
/// <param name="entity">实体对象</param>
void Save(object entity);
/// <summary>
/// 更新指定的实体对象
/// </summary>
/// <param name="entity">实体对象</param>
void Update(object entity);
/// <summary>
/// 获取指定ID值的实体对象
/// </summary>
/// <param name="type">对象类型</param>
/// <param name="id">ID值</param>
/// <returns>object</returns>
object Load(System.Type type,object id);
/// <summary>
/// 获取指定条件的实体对象列表
/// </summary>
/// <param name="type">实体类型</param>
/// <param name="expression">条件表达式对象</param>
/// <returns>System.Collections.IList</returns>
System.Collections.IList List(System.Type type,IExpression expression);
/// <summary>
/// 删除指定条件的实体对象
/// </summary>
/// <param name="type">实体类型</param>
/// <param name="expression">条件表达式对象</param>
void DeleteObjects(System.Type type,IExpression expression);
/// <summary>
/// 获取指定条件的数据集对象
/// </summary>
/// <param name="type">实体类型</param>
/// <param name="expression">条件表达式对象</param>
/// <returns>System.Data.DataSe</returns>
System.Data.DataSet ListDS(System.Type type,IExpression expression);
/// <summary>
/// 更新指定条件的数据
/// </summary>
/// <param name="type">实体类型</param>
/// <param name="expression">System.Data.DataSe</param>
/// <param name="fields">更新的字段列表</param>
void UpdateObjects(System.Type type,IExpression expression,params IField[] fields);
#endregion
}
从接口信息可以看到Connection,Transaction,BeginTran,Commit,RoolBack,ExecuteReader,ExecuteScalar等属性和方法的对象都是经常在操作数据库时用到的。为了使用方便通过接口把所有功能结合起来。当接口定义完成后我们就实现接口所描述功能DataSession。
/// <summary>
/// 数据处理环境
/// </summary>
public class DataSession:IDataSession
{
/// <summary>
/// 构造数据环境处理对象
/// </summary>
public DataSession()
{
//
// TODO: 在此处添加构造函数逻辑
//
}
/// <summary>
/// 构造指定数据设备的处理对象
/// </summary>
/// <param name="driverType">设备提供对象</param>
public DataSession(IDriverType driverType)
{
this.DriverType = driverType;
}
private Component component = new Component();
private IDriverType mDriverType;
/// <summary>
/// 获取或设置设备提供对象
/// </summary>
public IDriverType DriverType
{
get
{
return mDriverType;
}
set
{
mDriverType = value;
}
}
private string mConnectionString;
/// <summary>
/// 获取或设置数据库连接字符串
/// </summary>
public string ConnectionString
{
get
{
return mConnectionString;
}
set
{
mConnectionString = value;
}
}
#region IExecuteCmd 成员
private System.Data.IDbConnection mConnection;
/// <summary>
/// 获取数据连接对象
/// </summary>
public System.Data.IDbConnection Connection
{
get
{
// TODO: 添加 ExecuteSession.Connection getter 实现
return mConnection;
}
}
private System.Data.IDbTransaction mTransaction;
/// <summary>
/// 获取环境的事务处理对象
/// </summary>
public System.Data.IDbTransaction Transaction
{
get
{
// TODO: 添加 ExecuteSession.Transaction getter 实现
return mTransaction;
}
}
/// <summary>
/// 执行命令处理对象并返回数据读取器对象
/// </summary>
/// <param name="cmd">命令对象</param>
/// <returns>System.Data.IDataReader</returns>
public System.Data.IDataReader ExecuteReader(System.Data.IDbCommand cmd)
{
// TODO: 添加 ExecuteSession.System.Data.IDbCommand.ExecuteReader 实现
SetCommandState(cmd);
return cmd.ExecuteReader();
}
private void SetCommandState(System.Data.IDbCommand cmd)
{
cmd.Connection = mConnection;
if(mTransaction != null)
cmd.Transaction = mTransaction;
}
/// <summary>
/// 执行命令处理对象并返回记录集第一行第列的值
/// </summary>
/// <param name="cmd">命令对象</param>
/// <returns>object</returns>
public object ExecuteScalar(System.Data.IDbCommand cmd)
{
// TODO: 添加 ExecuteSession.ExecuteScalar 实现
SetCommandState(cmd);
return cmd.ExecuteScalar();
}
/// <summary>
/// 执行命令并返回受影响记录集的行数
/// </summary>
/// <param name="cmd">命令对象</param>
/// <returns>int</returns>
public int ExecuteNonQuery(System.Data.IDbCommand cmd)
{
// TODO: 添加 ExecuteSession.ExecuteNonQuery 实现
SetCommandState(cmd);
return cmd.ExecuteNonQuery();
}
/// <summary>
/// 执行命令对象并返回相应的数据集容器
/// </summary>
/// <param name="cmd">命令对象</param>
/// <returns>System.Data.DataSet</returns>
public System.Data.DataSet ExecuteDataSet(System.Data.IDbCommand cmd)
{
// TODO: 添加 ExecuteSession.ExecuteDataSet 实现
SetCommandState(cmd);
System.Data.IDbDataAdapter da = DriverType.DataAdapter;
da.SelectCommand = cmd;
System.Data.DataSet myDS = new System.Data.DataSet();
da.Fill(myDS);
return myDS;
}
/// <summary>
/// 批执行命令对象
/// </summary>
/// <param name="cmd">命令对象</param>
public void ExcuteCmds(params System.Data.IDbCommand[] cmd)
{
// TODO: 添加 ExecuteSession.ExcuteCmds 实现
if(cmd != null)
{
foreach(System.Data.IDbCommand item in cmd)
{
ExecuteNonQuery(item);
}
}
}
/// <summary>
/// 打开数据处理环境
/// </summary>
public void Open()
{
// TODO: 添加 ExecuteSession.Open 实现
mConnection = DriverType.Connection;
mConnection.ConnectionString =ConnectionString;
mConnection.Open();
}
/// <summary>
/// 关闭数据处理环境
/// </summary>
public void Close()
{
// TODO: 添加 ExecuteSession.Close 实现
mConnection.Close();
if(mTransaction != null)
mTransaction.Dispose();
}
/// <summary>
/// 在当前数据处理环境中打开事务
/// </summary>
public void BeginTran()
{
mTransaction = this.mConnection.BeginTransaction();
}
/// <summary>
/// 提交当前数据处理环境打开的事务
/// </summary>
public void Commit()
{
// TODO: 添加 ExecuteSession.Commit 实现
mTransaction.Commit();
mTransaction.Dispose();
mTransaction = null;
}
/// <summary>
/// 回滚当前数据处理环境打的事务
/// </summary>
public void RoolBack()
{
// TODO: 添加 ExecuteSession.RoolBack 实现
mTransaction.Rollback();
mTransaction.Dispose();
mTransaction = null;
}
#endregion
#region IDisposable 成员
/// <summary>
/// 释放放当前数据处理环境占用的资源
/// </summary>
public void Dispose()
{
// TODO: 添加 ExecuteSession.Dispose 实现
CDispose(true);
GC.SuppressFinalize(this);
}
private bool disposed = false;
private void CDispose(bool disposing)
{
if(mConnection != null)
{
if(this.mConnection.State != System.Data.ConnectionState.Closed)
{
this.mConnection.Close();
}
this.mConnection.Dispose();
this.mConnection =null;
}
if(!this.disposed)
{
if(disposing)
{
// Dispose managed resources.
component.Dispose();
}
}
disposed = true;
}
/// <summary>
/// 释放当前对象资源
/// </summary>
~DataSession()
{
CDispose(false);
}
#endregion
#region IDataSession 成员
private MappingContainer mContainer = null;
/// <summary>
/// 获取或设置对象映射容器
/// </summary>
public MappingContainer Container
{
get
{
// TODO: 添加 DataSession.Container getter 实现
return mContainer;
}
set
{
// TODO: 添加 DataSession.Container setter 实现
mContainer = value;
}
}
#endregion
#region IDataSession 成员
private void CheckType(string typename)
{
if(!Container.Mappings.Contains(typename))
throw(new MappingError("not found ObjectMap"));
}
/// <summary>
/// 保存指定的实体对象
/// </summary>
/// <param name="entity">实体对象</param>
public void Save(object entity)
{
System.Type type = entity.GetType();
CheckType(type.ToString());
doSave(entity,(ObjectMap)Container.Mappings[type.ToString()]);
}
private void doSave(object entity,ObjectMap map)
{
IPersistentCommand cachecmd = null;
object key = null;
try
{
System.Data.IDbCommand cmd = null;
System.Data.IDbCommand keycmd = DriverType.Command;
if(Container.Config.IsCacheCommand)
cachecmd = map.InsertCache.GetCommand();
if(map.IdInput && map.ID_SQL != null && map.IdProperty != null)
{
keycmd.CommandText = map.ID_SQL;
key = ExecuteNonQuery(keycmd);
map.IdProperty.SetValue(entity,key,null);
}
if(cachecmd == null)
{
cmd = map.Insert(entity,DriverType);
}
else
cmd = cachecmd.GetCommand(entity);
this.ExecuteNonQuery(cmd);
if(!map.IdInput && map.ID_SQL != null && map.IdProperty != null)
{
keycmd.CommandText = map.ID_SQL;
key = ExecuteScalar(keycmd);
map.IdProperty.SetValue(entity,System.Convert.ChangeType(key,map.IdProperty.PropertyType),null);
}
if(Container.Config.IsCacheCommand && cachecmd == null)
{
cachecmd = new PersistentInsertCommand();
cachecmd.Map = map;
cachecmd.Command = cmd;
map.InsertCache.SetCache(cachecmd);
}
}
catch(Exception e_)
{
throw(e_);
}
finally
{
if(cachecmd != null)
cachecmd.Release();
}
}
#endregion
………………..
………………….
}
DataSession实现了接口描述的主要功能,包括操作实现IDbCommand接口对象,事务处理和一些简单的O/R Mapping处理;因为涉及的代码也比较多,所以文中只把O/R Mapping的Insert插入对象的代码贴出来,其实其他的O/R Mapping操作方法也是大同小异也不会影响设计描述。数据访问的数据库提供者接口和数据操作接口描述完了,对于一个好的组件来说需要做的东西还很多;如在效率上的改进,缓存技术的运用等。这里所表达的只是一个很基础的东西。
在设计的过程对自己也有不少深刻的启发,总结有这几点:
1. 在设计前先明白组件需要完成什么,并通过接口制定下来;了解在完成这些功能涉及那方面的技术资料。
2. 运用接口编程,在你编写接口的同时其实也是对你组件结构的完善;有本书也提到用规则做事情总比没有的好和效率高。
3. 如果用到反射技术,最好缓存反射信息;虽然不是必要但性能上有很大提高。
4. 在设计自己的缓冲池机制时要认真考虑,因为这东西写得不好效果会更糟糕。
以上纯属个人的看法,并不能代表所现实中所有情况,如果有兴趣的朋友请发有自己的看法,多多交流。
转载来源:http://www.cnblogs.com/henryfan/archive/2005/09/17/238896.aspx