如何简化数据库的访问与操作,兼谈泛型的应用
2009-07-14 16:47
417 查看
一、一些说明及申明
不积跬步无以至千里,不纳滴水无以成江海。本文谈到的仅仅是实际开发过程的一个简单应用,虽然显得很不深奥,但并不影响一些编程思想在其中的具体体现。授之与鱼,不如授之与渔。我期望的是,通过某个简单应用的编码过程,让大家理解某种(个)技术的发明初衷,以及它的适用场景。可惜的是,这里大量的内容基于个人的经验总结和理解,毕竟书读得不够、不透,如果某些措辞不当或者指代混乱希望您见谅,好在不是正式出版物,草根说错话肯定比“专家”要少挨很多砖:)
不过两年多的工作经历,自我感觉技术上尚是浅薄,师弟、师妹们可多了解些我的工作心得,或许能少走弯路;至于大侠、小虾之辈,哂笑之后若肯指教一二最好,或者请以宽容之心释己之怀啊!
二、面向对象的抽象过程
在进行后台开发时,我们时常要进行数据库的访问与操作,.net提供了IDbConnection,IDbCommand以及IDbDataParameter等接口可以满足我们的日常需要。通常,我们这样获取数据或进行操作://ExecuteReader
using(SqlConnectioncon=newSqlConnection("myconnectionstring")){
using(SqlCommandcmd=newSqlCommand("mycommandstring",con)){
con.Open();
using(SqlDataReaderreader=cmd.ExecuteReader()){
while(reader.Read()){
//Todo
}
}
}
}
//ExecuteScalar
using(SqlConnectioncon=newSqlConnection("myconnectionstring")){
using(SqlCommandcmd=newSqlCommand("mycommandstring",con)){
con.Open();
objectresult=cmd.ExecuteScalar();
}
}
//ExecuteNonQuery
using(SqlConnectioncon=newSqlConnection("myconnectionstring")){
using(SqlCommandcmd=newSqlCommand("mycommandstring",con)){
con.Open();
intresult=cmd.ExecuteNonQuery();
}
}
我在做了相当长一段时间如上编码之后,发觉可以进行如下简化,创建SqlHelper进行辅助。相信当前很多开发人员也是这么做的(指思路相似,细节处肯定不同):
classSqlHelper{
publicstaticSqlDataReaderExecuteReader(stringconStr,stringcmdStr){
//ExecuteReader
SqlConnectioncon=newSqlConnection(conStr);
SqlCommandcmd=newSqlCommand(cmdStr,con);
con.Open();
returncmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection);//这表示操作完成后关闭该Reader
}
publicstaticobjectExecuteScalar(stringconStr,stringcmdStr){
//ExecuteScalar
using(SqlConnectioncon=newSqlConnection(conStr)){
using(SqlCommandcmd=newSqlCommand(cmdStr,con)){
con.Open();
returncmd.ExecuteScalar();
}
}
}
publicstaticintExecuteNonQuery(stringconStr,stringcmdStr){
//ExecuteNonQuery
using(SqlConnectioncon=newSqlConnection(conStr)){
using(SqlCommandcmd=newSqlCommand(cmdStr,con)){
con.Open();
returncmd.ExecuteNonQuery();
}
}
}
}
当需要传参时,要么通过string.format()等方法拼接cmdStr(很不安全),要么通过原始方法,或者通过如下变通方案:
structParameter{
publicstringName{get;set;}
publicobjectValue{get;set;}
}
publicstaticintExecuteNonQuery(stringconStr,stringcmdStr,paramsParameter[]pars){
//ExecuteNonQuery
using(SqlConnectioncon=newSqlConnection(conStr)){
using(SqlCommandcmd=newSqlCommand(cmdStr,con)){
foreach(Parameterpinpars){
cmd.Parameters.AddWithValue(p.Name,p.Value);
}
con.Open();
returncmd.ExecuteNonQuery();
}
}
}
到这一步,看上去数据库访问已经简化得差不多:
intresult=SqlHelper.ExecuteNonQuery("myconnectionstring","mycommandstring",
newParameter(){Name="Number",Value=1},newParameter(){Name="Text",Value="afaefawf"});
或者只是一些细节上的优化:
classSqlHelperII{
publicstaticSqlHelperIIInstanceForStudentsSystem{get;privateset;}
staticSqlHelperII(){
InstanceForStudentsSystem=newSqlHelperII(System.Configuration.ConfigurationSettings
.AppSettings["StudentsSystemConnectionString"]);
}
publicSqlHelperII(stringconnectionString){
this.ConnectionString=connectionString;
}
publicstringConnectionString{get;privateset;}
publicintExecuteNonQuery(stringcmdStr,paramsstring[]pars){
//ExecuteNonQuery
using(SqlConnectioncon=newSqlConnection(this.ConnectionString)){
using(SqlCommandcmd=newSqlCommand(cmdStr,con)){
for(inti=0;i<pars.Length;i+=2){
cmd.Parameters.AddWithValue(pars[i],pars[i+1]);
}
con.Open();
returncmd.ExecuteNonQuery();
}
}
}
}
以往的操作可以这样进行:
intresult=SqlHelperII.InstanceForStudentsSystem.ExecuteNonQuery("mycommandstring",
"Number",1.ToString(),"Text","afaefawf");
事实上,一般情况下我个人认为这样确实足够了,但上面的代码全假设在了SqlServer数据库之上,一旦遇到MySql之类,就需要编写某个MySqlHelper等。另外,我在年初某个项目与MID层进行交互时突然悟到,其实任何数据类操作都可以抽象成类数据库的访问方式;当时我为此做了个MIDServicesHelper,其实现了ExecuteReader,ExecuteScalar,ExecuteNonQuery三个方法(我的实践证明,这三个方法大概是足够用了)。
接下来的时间里,我创建了这样的一个接口IDbExecutor:
namespaceQueen.Data{
///<summary>
///数据库服务接口,负责进行数据的存储、读取等基本操作.
///它可以是一个传统的Sql数据库,也可以是其他支持数据操作的系统.
///</summary>
publicinterfaceIDbExecutor{
///<summary>
///链接串
///</summary>
stringConnectionString{
get;
set;
}
///<summary>
///超时设置
///</summary>
intCommandTimeout{
get;
set;
}
///<summary>
///获取执行影响行数.
///实现该接口时注意释放所有相关资源!
///</summary>
///<paramname="cmdText"></param>
///<paramname="keyValuePairs"></param>
///<returns></returns>
intExecuteNonQuery(stringcmdText,paramsstring[]keyValuePairs);
///<summary>
///获取执行值.
///实现该接口时注意释放所有相关资源!
///</summary>
///<paramname="cmdText"></param>
///<paramname="keyValuePairs"></param>
///<returns></returns>
objectExecuteScalar(stringcmdText,paramsstring[]keyValuePairs);
///<summary>
///获取数据读取流.
///调用该接口时务必释放读取流,using(IDataReaderreader=XX.ExecuteReader()){...}
///实现该接口时请勿释放任何资源,返回调用System.Data.SqlCommand.ExecuteReader(
System.Data.CommandBehavior.CloseConnection)
///</summary>
///<paramname="cmdText">数据库执行文本.</param>
///<paramname="keyValuePairs">参数组,比如"@ParaA","valueA","@ParaB","valueB"</param>
///<returns>数据读出流</returns>
System.Data.IDataReaderExecuteReader(stringcmdText,paramsstring[]keyValuePairs);
}
}
凡后来我主导的项目里总会多出这样一个文件及类型DbExecutor,其内部可定义支持SqlServer、MySql甚至其它数据库的内部类,但数据库类型对外界“基本”透明(组织SQL语句时必须知道数据库类型,SqlServer、MySql有各自的SQL语言特性!):
staticclassDbExecutor{
publicstaticQueen.Data.IDbExecutorStudents{get;privateset;}
publicstaticQueen.Data.IDbExecutorFamilies{get;privateset;}
staticDbExecutor(){
//Initstudents,families...
}
privateclassSqlServerExecutor:Queen.Data.IDbExecutor{
#regionIDbExecutor成员
publicstringConnectionString{
get{
thrownewNotImplementedException();
}
set{
thrownewNotImplementedException();
}
}
publicintCommandTimeout{
get{
thrownewNotImplementedException();
}
set{
thrownewNotImplementedException();
}
}
publicintExecuteNonQuery(stringcmdText,paramsstring[]keyValuePairs){
thrownewNotImplementedException();
}
publicobjectExecuteScalar(stringcmdText,paramsstring[]keyValuePairs){
thrownewNotImplementedException();
}
publicSystem.Data.IDataReaderExecuteReader(stringcmdText,paramsstring[]keyValuePairs){
thrownewNotImplementedException();
}
#endregion
}
privateclassMySqlExecutor:Queen.Data.IDbExecutor{
#regionIDbExecutor成员
publicstringConnectionString{
get{
thrownewNotImplementedException();
}
set{
thrownewNotImplementedException();
}
}
publicintCommandTimeout{
get{
thrownewNotImplementedException();
}
set{
thrownewNotImplementedException();
}
}
publicintExecuteNonQuery(stringcmdText,paramsstring[]keyValuePairs){
thrownewNotImplementedException();
}
publicobjectExecuteScalar(stringcmdText,paramsstring[]keyValuePairs){
thrownewNotImplementedException();
}
publicSystem.Data.IDataReaderExecuteReader(stringcmdText,paramsstring[]keyValuePairs){
thrownewNotImplementedException();
}
#endregion
}
}
这样做的好处在于,不但在代码量上减少了开发人员的维护成本,也在系统的迁移上极大减少了代码替换工作(虽然迁移的可能性比较小),同时,也给系统的数据库操作监控带来了便利(比如增加日志功能等,后文将举例实现)。
三、泛型编程
但针对数据库操作这块,我还是严重感觉不够优雅,我觉得SqlServerExecutor、MyServerExecutor等虽然引用了不同的驱动程序,但这些驱动程序均实现了同一类接口(IDbConnection等),通过继承实现多个版本的XxxExecutor简直太丑陋了!而后,通过学习、领会泛型编程思想,我当前使用的版本产生了:usingSystem.Data;
namespaceQueen.Data{
///<summary>
///提供Sql语言支持的数据库服务,必须声明为IDbExecutor才能使用相关服务.
///</summary>
///<typeparamname="TConnection">链接类型</typeparam>
///<typeparamname="TCommand">命令类型</typeparam>
///<typeparamname="TDataParameter">数据参数类型</typeparam>
publicclassSqlExecutor<TConnection,TCommand,TDataParameter>:IDbExecutor
whereTConnection:IDbConnection,new()
whereTCommand:IDbCommand,new()
whereTDataParameter:IDbDataParameter,new(){
///<summary>
///Sql命令类型.
///</summary>
publicSystem.Data.CommandTypeCommandType{
get;
set;
}
#regionConstruct
publicSqlExecutor(){
CommandType=System.Data.CommandType.Text;
((IDbExecutor)this).CommandTimeout=30;
}
publicSqlExecutor(System.Data.CommandTypecommandType){
CommandType=commandType;
((IDbExecutor)this).CommandTimeout=30;
}
publicSqlExecutor(System.Data.CommandTypecommandType,intcommandTimeout){
CommandType=commandType;
((IDbExecutor)this).CommandTimeout=commandTimeout;
}
#endregion
#regionISqlExecutor成员
stringIDbExecutor.ConnectionString{
get;
set;
}
///<summary>
///链接串,默认30(秒)
///</summary>
intIDbExecutor.CommandTimeout{
get;
set;
}
intIDbExecutor.ExecuteNonQuery(stringcmdText,paramsstring[]keyValuePairs){
using(TConnectioncon=newTConnection()){
con.ConnectionString=((IDbExecutor)this).ConnectionString;
using(TCommandcmd=newTCommand()){
cmd.CommandText=cmdText;
cmd.Connection=con;
if(keyValuePairs!=null){
for(inti=0;i<keyValuePairs.Length-1;i+=2){
TDataParameterp=newTDataParameter();
p.ParameterName=keyValuePairs[i];
p.Value=keyValuePairs[i+1];
cmd.Parameters.Add(p);
}
}
cmd.CommandType=CommandType;
cmd.CommandTimeout=((IDbExecutor)this).CommandTimeout;
con.Open();
returncmd.ExecuteNonQuery();
}
}
}
objectIDbExecutor.ExecuteScalar(stringcmdText,paramsstring[]keyValuePairs){
using(TConnectioncon=newTConnection()){
con.ConnectionString=((IDbExecutor)this).ConnectionString;
using(TCommandcmd=newTCommand()){
cmd.CommandText=cmdText;
cmd.Connection=con;
if(keyValuePairs!=null){
for(inti=0;i<keyValuePairs.Length-1;i+=2){
TDataParameterp=newTDataParameter();
p.ParameterName=keyValuePairs[i];
p.Value=keyValuePairs[i+1];
cmd.Parameters.Add(p);
}
}
cmd.CommandType=CommandType;
cmd.CommandTimeout=((IDbExecutor)this).CommandTimeout;
con.Open();
returncmd.ExecuteScalar();
}
}
}
///<summary>
///获取数据读取流
///调用该接口时务必释放读取流,using(IDataReaderreader=XX.ExecuteReader()){...}.
///</summary>
///<paramname="cmdText"></param>
///<paramname="keyValuePairs"></param>
///<returns></returns>
System.Data.IDataReaderIDbExecutor.ExecuteReader(stringcmdText,paramsstring[]keyValuePairs){
TConnectioncon=newTConnection();
con.ConnectionString=((IDbExecutor)this).ConnectionString;
TCommandcmd=newTCommand();
cmd.CommandText=cmdText;
cmd.Connection=con;
if(keyValuePairs!=null){
for(inti=0;i<keyValuePairs.Length-1;i+=2){
TDataParameterp=newTDataParameter();
p.ParameterName=keyValuePairs[i];
p.Value=keyValuePairs[i+1];
cmd.Parameters.Add(p);
}
}
cmd.CommandType=CommandType;
cmd.CommandTimeout=((IDbExecutor)this).CommandTimeout;
con.Open();
returncmd.ExecuteReader(CommandBehavior.CloseConnection);
}
#endregion
}
}
只需指定TConnection,TCommand,TDataParameter的具体类型,该辅助类并能支持对应工作了;也就是把SqlServer、MySql等的具体辅助类合并到了一起并做了实现,真的是一劳永逸啊!相应的,DbExecutor简化成了:
///<summary>
///数据访问代理
///</summary>
publicstaticclassDbExecutor{
privatestaticIDbExecutor_files;
publicstaticIDbExecutorFiles{get{return_files;}privateset{_files=value;}}
privatestaticIDbExecutor_BK;
publicstaticIDbExecutorBK{get{return_BK;}privateset{_BK=value;}}
privatestaticIDbExecutor_ResourcesProcedure;
publicstaticIDbExecutorResourcesProcedure{get{return_ResourcesProcedure;}privateset{
_ResourcesProcedure=value;}}
privatestaticIDbExecutor_Resources;
publicstaticIDbExecutorResources{get{return_Resources;}privateset{_Resources=value;}}
staticDbExecutor(){
Files=newSqlExecutor<MySqlConnection,MySqlCommand,MySqlParameter>();
Files.ConnectionString=System.Configuration.ConfigurationManager.ConnectionStrings["Files"]
.ConnectionString;
BK=newSqlExecutor<MySqlConnection,MySqlCommand,MySqlParameter>();
BK.ConnectionString=System.Configuration.ConfigurationManager.ConnectionStrings["bk"]
.ConnectionString;
Resources=newSqlExecutor<MySqlConnection,MySqlCommand,MySqlParameter>();
Resources.ConnectionString=System.Configuration.ConfigurationManager.ConnectionStrings[
"Resources"].ConnectionString;
ResourcesProcedure=newSqlExecutor<MySqlConnection,MySqlCommand,MySqlParameter>(
System.Data.CommandType.StoredProcedure);
ResourcesProcedure.ConnectionString=System.Configuration.ConfigurationManager
.ConnectionStrings["Resources"].ConnectionString;
}
}
四、装饰者设计模式的应用
最近,组内希望添加数据库操作人员记录,我只是做了如下的改动便完成了简易日志功能(请参见“装饰者设计模式”):usingSystem;
usingSystem.Collections.Generic;
usingSystem.Text;
usingQueen.Data;
usingMySql.Data.MySqlClient;
usingSystem.Web;
///<summary>
///数据访问代理
///</summary>
publicstaticclassDbExecutor{
privatestaticIDbExecutor_files;
publicstaticIDbExecutorFiles{get{return_files;}privateset{_files=value;}}
privatestaticIDbExecutor_BK;
publicstaticIDbExecutorBK{get{return_BK;}privateset{_BK=value;}}
privatestaticIDbExecutor_ResourcesProcedure;
publicstaticIDbExecutorResourcesProcedure{get{return_ResourcesProcedure;}
privateset{_ResourcesProcedure=value;}}
privatestaticIDbExecutor_Resources;
publicstaticIDbExecutorResources{get{return_Resources;}privateset{_Resources=value;}}
staticDbExecutor(){
Files=newSqlExecutor<MySqlConnection,MySqlCommand,MySqlParameter>();
Files.ConnectionString=System.Configuration.ConfigurationManager.ConnectionStrings["Files"]
.ConnectionString;
Files=newDecorateExecutor(Files);
BK=newSqlExecutor<MySqlConnection,MySqlCommand,MySqlParameter>();
BK.ConnectionString=System.Configuration.ConfigurationManager.ConnectionStrings["bk"]
.ConnectionString;
BK=newDecorateExecutor(BK);
Resources=newSqlExecutor<MySqlConnection,MySqlCommand,MySqlParameter>();
Resources.ConnectionString=System.Configuration.ConfigurationManager
.ConnectionStrings["Resources"].ConnectionString;
Resources=newDecorateExecutor(Resources);
ResourcesProcedure=newSqlExecutor<MySqlConnection,MySqlCommand,MySqlParameter>(
System.Data.CommandType.StoredProcedure);
ResourcesProcedure.ConnectionString=System.Configuration.ConfigurationManager
.ConnectionStrings["Resources"].ConnectionString;
ResourcesProcedure=newDecorateExecutor(ResourcesProcedure);
}
#regionDecorateExecutor,logable
classDecorateExecutor:IDbExecutor{
privateIDbExecutorvalue;
publicDecorateExecutor(IDbExecutorvalue){
this.value=value;
}
#regionIDbExecutor成员
publicintCommandTimeout{
get{
returnthis.value.CommandTimeout;
}
set{
this.value.CommandTimeout=value;
}
}
publicstringConnectionString{
get{
returnthis.value.ConnectionString;
}
set{
this.value.ConnectionString=value;
}
}
publicintExecuteNonQuery(stringcmdText,paramsstring[]keyValuePairs){
if(null!=HttpContext.Current&&null!=HttpContext.Current.User&&null
!=HttpContext.Current.User.Identity&&!string.IsNullOrEmpty(HttpContext.Current.User.Identity.Name)){
System.Diagnostics.Trace.WriteLine(string.Format("{0}\t{1}进行了操作{2}...",
DateTime.Now.ToShortTimeString(),HttpContext.Current.User.Identity.Name,
cmdText.Length>20?cmdText.Substring(0,20):cmdText));
}
returnthis.value.ExecuteNonQuery(cmdText,keyValuePairs);
}
publicSystem.Data.IDataReaderExecuteReader(stringcmdText,paramsstring[]keyValuePairs){
if(null!=HttpContext.Current&&null!=HttpContext.Current.User&&null
!=HttpContext.Current.User.Identity&&!string.IsNullOrEmpty(HttpContext.Current.User.Identity.Name)){
System.Diagnostics.Trace.WriteLine(string.Format("{0}\t{1}进行了操作{2}...",
DateTime.Now.ToShortTimeString(),HttpContext.Current.User.Identity.Name,
cmdText.Length>20?cmdText.Substring(0,20):cmdText));
}
returnthis.value.ExecuteReader(cmdText,keyValuePairs);
}
publicobjectExecuteScalar(stringcmdText,paramsstring[]keyValuePairs){
if(null!=HttpContext.Current&&null!=HttpContext.Current.User&&null
!=HttpContext.Current.User.Identity&&!string.IsNullOrEmpty(HttpContext.Current.User.Identity.Name)){
System.Diagnostics.Trace.WriteLine(string.Format("{0}\t{1}进行了操作{2}...",
DateTime.Now.ToShortTimeString(),HttpContext.Current.User.Identity.Name,
cmdText.Length>20?cmdText.Substring(0,20):cmdText));
}
returnthis.value.ExecuteScalar(cmdText,keyValuePairs);
}
#endregion
}
#endregion
}
五、小结:
优雅的代码让人看着赏心悦目,虽然每个人的审美会有不同,但至少要让自己觉得舒服。我尝试着在编码的过程中积累软件设计的经验,充分发挥面向对象的长处,使得代码的可复用性、可维护性(可调试的能力?)、可扩展性达到极致。在本文前半段,我与大家分享了我在抽象数据库访问行为时的心得(不过是IDbExecutor接口),而在接下来的文字里,我使用了泛型编程技巧,使得传统的面向对象(通过继承等手段扩展、复用代码?)得到了看似质的突破,极大缓解了“继承泛滥”的苦恼。面向过程编程,大量的操作是参数的传递,由于参数之间有逻辑上的关联,且“过程”本身又针对某类关联参数,于是有了“面向对象”。这样一来,人们掌握系统全局的能力得到了加强,从.net框架提供的各种服务来看,面向对象实质上降低了编程的门槛,并且提高了人们的生产效率。
通常,除非使用反射,我们很难使得代码不经过扩展而达到“向后兼容”。但反射本身并不优雅,而且执行效率会降低,泛型的推出解决了这样一个矛盾。我个人的理解是,可以将所谓“泛型”看成“类型参数”,传统上,参数基本是基于数据的,而引入了类型参数后,不但不会失去强类型的类型安全特点,还能使得代码被编译器优化,提高执行效率(是指“值类型的装箱、拆箱”操作等)。
但从来没有完美的事,在当真要进行数据库转换、系统迁移的时候,比如从SqlServer变为MySql时,当前的IDbExecutor是不可能让数据库类型透明的:因为开发人员在组织SQL语句时必须知道数据库类型,SqlServer、MySql有各自的SQL语言特性,相同的SQL语句并不能保证在两个数据库平台上成功运行!解决这个缺陷的办法,除了阉割当前数据库的SQL语言特性外(指禁止使用),我看是无解的;但分页操作(关键字TOP/LIMIT)是无法回避的,而且,在SqlServer中参数使用
(完)
相关文章推荐
- servlet 定时操作,定时访问数据库,程序应该如何实现
- 如何在Django WEB应用内跟踪数据库的save操作
- (3)uniGUI for C++ builder手机应用开发之ORACLE数据库访问与操作
- ndoejs如何访问数据库,操作数据库
- 数据库应用安全:如何平衡加密与访问控制
- Android开发—数据库应用—访问数据表(SQLite OpenHelper) —添加检索操作(Retrieve)
- 在magento下如何直接操作访问数据库
- 如何通过AgileEAS.NET快速搭建属于你的企业应用(二)——智能版本升级和多数据库访问的分布式部署
- 数据库应用安全:如何平衡加密与访问控制
- 如何建立JSP操作用以提高数据库访问效率
- qtp,VBScript操作MySQL数据库时,关于多次访问数据库的问题,数据集如何处理
- Sqlserver 在查询分析器里如何访问远程的的数据库,进行数据查询更新等操作。
- SSH2框架--使用泛型DAO,JUnit测试时,如何使用事务,使用操作不真正的提交的数据库中
- vb.net 数据库访问操作
- 如何查找Oracle数据库service_name、及可访问数据库的用户名密码
- Linux系统下授权MySQL账户访问指定数据库和数据库操作
- 如何直接使用ODBC提供的API来操作数据库呢?
- 从“如何设计用户超过1亿的应用”说起—数据库调优实战
- 数据库的插入操作中如何减少和数据库的交互
- 如何把现有的数据库转换到membership上应用!