说起ADO.NET,就扯上了数据库访问类库了,现在的每个项目的数据库访问类应该说都很强的了,经常就听到说我的我们的数据库访问类怎么怎么强大而且支持多数据库,现在的大家做的项目里用的数据库访问类库我想也都是支持多数据库吧,支持到什么程度我就不知道了。可能只是那么想也是那么设计的,要支持多数据库,要能支持多数据库,万一要是以后数据库变了怎么办?万一要是。。。怎么办?这些顾虑很多时候是不必要的,反而绕了弯子。大都是做项目应用系统而非产品,即使要用不同的数据库了,基本上是吧上一个项目全COPY过来,修修改改OK了。产品可能就不一样了,那才可能要支持真正的多数据库,才可能会面对真正的数据库访问类库的多数据库的实际检验。ADO.NET2.0下增强了数据库访问的功能,也就是工厂式类库,提到工厂式数据库访问,网上可就多了,ADO.NET2.0增强的工厂式网上也很多了,都说只要改动webconfig里的数据库连接就行了,其它什么地方都不用改了,看了几篇都是点了下,不知道做过充分测试没有,应该说在实际的多数据库产品系统中,还要做很多修正,完善和测试的。
说正题,假设(只是假设,真的不会这么变态,呵呵)一产品系统要支持ACCESS,SYBASE,SQL SERVER,ORACEL数据库,系统开发完后,要求只改动数据库的连接,也就是说只改动webconfig,来实现系统的无缝切换,其它什么也不改动,可能有的觉得简单,但是要经得起实际检验还是要花点时间测试的,当然写是不算难,见到过有的应用系统开发中,所有的DML语句都以XML形式放在config配置文件里,然后用封装好的数据库访问组件读config配置文件执行SQL语句,开发人员只要建个config文件把增删改查语句写那里就可以,当然这么来有个好处,就是做到了与数据库的无关性了,如果数据库访问组件做的完善的,当然是可以做到系统无缝切换到其它数据库的,当然同时也有缺陷,项目中不仅仅是DML语句吧,有的稍微复杂点的逻辑,存储过程要用下吧,我自己也趋向于喜欢用存储过程,自定义函数来处理稍微复杂的逻辑,而且把DML语句都放在配置文件里我也是不能忍受的,可能是一个个人的习惯吧。
publicDbConnectionconn;//抽象类型
privateDbCommandcmd;//抽象类型
privateDbProviderFactoryprovider;
privateDbParameterPara;//不同数据库参数类型的抽象类型
privateDbDataAdapterAdapter;//对应不同数据库的数据适配器
Dictionary<Type,String>ParametersFormat;//不同数据库参数格式化类型
publicstringretParaformat=string.Empty;//最终返回的格式化标志,如@{0},:{0}
publicDataProviderFactory()
{
//从配置文件中取出标示数据库类型的字符串并通过ProviderName的不同支持不同类型的数据库
stringproviderName=ConfigurationManager.ConnectionStrings["ConnStr"].ProviderName;//也可以用索引,从1开始
//创建一个数据库对应的实例,使用该实例就可以创建对应的connection,command和adapater等等对象
provider=DbProviderFactories.GetFactory(providerName);
//创建具体的数据库连接类型和命令执行类型
conn=provider.CreateConnection();
conn.ConnectionString=ConfigurationManager.ConnectionStrings["ConnStr"].ConnectionString;
cmd=provider.CreateCommand();
cmd.Connection=conn;
//创建具体的参数类型
Para=provider.CreateParameter();
//创建具体的适配器类型
Adapter=provider.CreateDataAdapter();
//不同数据库参数前缀格式化
ParametersFormat=newDictionary<Type,String>();
ParametersFormat.Add(typeof(System.Data.SqlClient.SqlCommand),"@{0}");//因SQLSERVER只返回{0}没有@前缀,在此初始化处理
//返回格式化标志
retParaformat=GetParameterFormat(cmd);
}
上面那段代码中,可以看到我定义了两个公共变量,其中conn在外部只会有一个地方调用它,那就就是执行DataReader方法的时候了,因为大家都知道dr在离开方法体时,连接是不能关闭的,
所以只能在外部调用处显示关闭连接对象,必须定义为公共类型了,还有一个公共参数变量是格式化字符串的字符型。前面说到Oracle参数前缀是冒号:,其它几个数据库前缀是@符号,怎么样在切换数据库以后程序能动态识别参数前缀并组合相应的参数变量呢?如果手动写代码用数据库对象类型枚举去一个个判断,那这数据库工厂也没什么意义了,最终找到了一个相当完美的解决方式(微软就是微软,都能替你想到,别人想不强大都难啦,呵呵),其实在做测试的时侯到各不同数据库的参数前缀这就有点犯难了,手写代码一个个处理吧,没问题,觉得应该有简单的方法吧,MS从来就不是傻瓜呀,正好前两天在园子首页就有篇提到这个问题,而且给出相关提示,根据给的提示再gg了一把,终于找到了一个便捷的办法。还是贴方法代码好了,如下:
///<summary>
///根据不同的数据库命令对象返回该类型数据库参数的前缀格式化字符串
///</summary>
///<paramname="command"></param>
///<returns></returns>
privatestringGetParameterFormat(DbCommandcommand)
{
if(!ParametersFormat.ContainsKey(command.GetType()))
{
this.Open();//读取参数前缀时需打开数据库连接
ParametersFormat.Add(
command.GetType(),
command.Connection.GetSchema("DataSourceInformation")
.Rows[0]["ParameterMarkerFormat"].ToString());
//conn.Close();在真正执行语句的时候去关闭,避免重复打开
}
returnParametersFormat[command.GetType()];
}
就是这个了ParameterMarkerFormat,即参数标志符格式化,如连接oracle数据库则返回:{0},其它几个数据库返回@{0},惟独SQL SERVER数据库返回{0},到底是MS自己的东西,就是要返回跟别人不一样的东西,也就因为这个,这个类库里很遗憾不得不出现一个SqlCommand,就是上面贴出的构造函数里的初始化那ParametersFormat.Add(typeof(System.Data.SqlClient.SqlCommand), "@{0}");必须这样做下处理,另外包括GetParameterFormat方法里的判断,即不是SQL SERVER数据库时才去读参数前缀,如果是就直接返回@{0},有了这个格式化的前缀字符串,就好办了.那参数名称的赋值就可以类似这样了string.Format("@{0}", ParaName);
下面说说各通用的方法和调用,之前的sqlhelper.cs,oraclehelper.cs,xxhelper.cs中的执行方法大多都很多,有带参数执行的语句的方法,不带参数执行的语句的方法,带参数执行的方法体里面还要循环参数,这些都我都精简掉了,最终演变成了peacehelper.cs(开个玩笑).带参执行和不带参执行DML语句,其实是可以合并成一个方法,各个参数都是保存在数据库命令对象的参数集合中的,我们可以把创建好的命令对象返回给外部程序调用处,调用的地方要带参执行语句的话,就定义参数并赋值就行了,不带参执行的话就不用定义参数了,这么以来就只需要写一个方法就行了,而且执行带掺的语句时不用再循环参数集合了,因为在调用处定义参数时,该参数已经绑定都了DbCommand对象了.写一个返回给外部调用的数据库命令对象的方法,如下:
///<summary>
///抽象参数集合类型
///</summary>
///<returns></returns>
publicDbParameterCollectionGetParmCollection()
{
returncmd.Parameters;
}
添加参数的方法如下:
///<summary>
///添加参数
///</summary>
///<paramname="ParaName">参数名称</param>
///<paramname="SqlType">参数数据类型</param>
///<paramname="ParaValue">参数值</param>
///<paramname="ParaCollect">参数对象的集合</param>
publicvoidAddParam(stringParaName,DbTypeSqlType,objectParaValue,DbParameterCollectionParaCollect)
{
//不允许将一个DbCommand对象的Parameters插入到另外一个DbCommand对象,那么多个参数的话可以加上下面一句判断
//如果已经存在至少一个对象时,再深层拷贝一个
if(ParaCollect.Count>=1)
{
Para=(DbParameter)((ICloneable)ParaCollect[0]).Clone();
}
Para.ParameterName=string.Format(retParaformat,ParaName);
Para.DbType=SqlType;
if(ParaValue==null)
{
Para.Value=string.Empty;//DBNull.Value;
}
else
{
Para.Value=ParaValue;
}
ParaCollect.Add(Para);
}
上面有句判断,如果有多个参数会出异常,网上搜了下,注释就是网上的解释,不多说了,意思很清楚。这个方法里还有一点,如果DbType参数不要的话测试也是可以通过的,猜想如果不显示指定参数数据类型的话,是不是都默认为object类型?这样的话会不会涉及一个装拆箱的操作呢?但是开发人员在调用处添加参数,是不应该关心参数的数据类型才对,干脆数据类型参数不要了,改成如下方法了:
publicvoidAddParam(stringParaName,objectParaValue,DbParameterCollectionParaCollect)
{
if(ParaCollect.Count>=1)
{
Para=(DbParameter)((ICloneable)ParaCollect[0]).Clone();
}
Para.ParameterName=string.Format(retParaformat,ParaName);//将参数格式化为具体的数据库参数格式
if(ParaValue==null)
{
Para.Value=string.Empty;
}
else
{
Para.Value=ParaValue;
}
ParaCollect.Add(Para);
}
为了兼容不同的数据库(主要是oracle变量特殊问题),添加参数的方法分两种,一种是普通带参执行的DML语句,一种是代参执行的存储过程。对于SQL SERVER数据库即使是存储过程
变量参数仍是@前缀,ORACLE存储过程又是什么前缀呢?很遗憾,ORACLE存储过程的参数变量是不需要任何前缀的,为了单独兼容这一点,对于不同数据库如果调用的存储过程有参数
的话,建议用下面的三个添加参数的方法:
///<summary>
///存储过程输入参数
///</summary>
///<paramname="ParaName"></param>
///<paramname="ParaValue"></param>
///<paramname="ParaCollect"></param>
publicvoidAddInputParam(stringParaName,objectParaValue,DbParameterCollectionParaCollect)
{
if(ParaCollect.Count>=1)
{
Para=(DbParameter)((ICloneable)ParaCollect[0]).Clone();
}
Para.ParameterName=string.Format(retParaformat.Replace(":",""),ParaName);//ORACLE存储过程参数前没有冒号
if(ParaValue==null)
{
Para.Value=string.Empty;
}
else
{
Para.Value=ParaValue;
}
ParaCollect.Add(Para);
}
///<summary>
///存储过程输出参数
///</summary>
///<paramname="ParaName"></param>
///<paramname="ParaValue"></param>
///<paramname="ParaCollect"></param>
publicvoidAddOutputParam(stringParaName,DbParameterCollectionParaCollect)
{
if(ParaCollect.Count>=1)
{
Para=(DbParameter)((ICloneable)ParaCollect[0]).Clone();
}
Para.ParameterName=string.Format(retParaformat.Replace(":",""),ParaName);
Para.Value=string.Empty;
ParaCollect.Add(Para);
ParaCollect[Para.ParameterName].Direction=System.Data.ParameterDirection.Output;//指定该参数为输出参数
}
///<summary>
///存储过程返回值参数
///</summary>
///<paramname="ParaName"></param>
///<paramname="ParaValue"></param>
///<paramname="ParaCollect"></param>
publicvoidAddReturnParam(stringParaName,DbParameterCollectionParaCollect)
{
if(ParaCollect.Count>=1)
{
Para=(DbParameter)((ICloneable)ParaCollect[0]).Clone();
}
Para.ParameterName=string.Format(retParaformat.Replace(":",""),ParaName);
Para.Value=string.Empty;
ParaCollect.Add(Para);
ParaCollect[Para.ParameterName].Direction=System.Data.ParameterDirection.ReturnValue;//指定该参数为返回值参数
}
OK,现在开始说下peacehelper.cs里的八大方法(其实算起来应该是10个),应该来说涵盖绝大多应该系统操作数据库的绝大部分功能,如果有特殊的操作可以在此基础上添加。
第一个,大家都熟悉的返回结果集:
///<summary>
///执行SQL并返回数据集
///</summary>
///<paramname="sql"></param>
///<returns></returns>
publicDataSetExecDataSet(stringSql)
{
DataSetds=newDataSet();
try
{
this.Open();
cmd.CommandText=Replace(Sql);
Adapter.SelectCommand=cmd;
Adapter.Fill(ds);
}
catch(Exceptionex)
{
throwex;
}
finally
{
this.Close();
}
returnds;
}
上面的方法大家看了是不是觉得既简单又熟悉,确实是的,但仍然相当以前的xxhelper.cs里是做了简化的,该方法既可以直接执行不带参DML语句,也可以执行带参的,但是该方法的形参却
只有一个,之前的xxhelper.cs里带参执行的话,形参中大多至少还另外一个形参的,比如SqlPeremeters[]类型或参数集合类型的形参,而且方法体里面大多会循环读取参数,上面的方法里
却没有,都简化掉了,唯一多了一点的是,所执行的命令语句执行前要做一个特殊字符替换,cmd.CommandText = Replace(Sql),Replace方法主要是替换参数前缀,Replace方法如下:
///<summary>
///替换DML语句里的参数前缀
///</summary>
///<paramname="str"></param>
///<returns></returns>
publicstringReplace(stringstr)
{
returnstr.Replace("$",retParaformat.Substring(0,1));
}
因为不同数据库除了在添加参数时有前缀的区别,再具体执行语句时也有前缀区别嘛,比如SQL SERVER里 SELECT USER_NAME,USER_AGE FROM USERS WHERE USER_ID=@USER_ID,ORACLE里是这样的SELECT USER_NAME,USER_AGE FROM USERS WHERE USER_ID=:USER_ID,在此就需要统一一个前缀规则了,统一这样SELECT USER_NAME,USER_AGE FROM USERS WHERE USER_ID=$USER_ID,在执行前根据不同数据库替换前缀$符号,当然这个约定规则不一定是最完美的,也许还存在一定的问题,写到这我也突然想起来之前我见过别人
的系统中有的就是变量参数用的这种类似特殊符号,肯定也是为了兼容多数据库所作的处理了,呵呵,具体的调用及测试之后统一说明。还有几个方法也和上面类似,大家都熟悉的。如下(不再做具体解释了):
Code
///<summary>
///执行SQL语句并返回DataReader对象
///</summary>
///<paramname="dbcon"></param>
///<paramname="cmdText"></param>
///<returns></returns>
publicDbDataReaderExecuteDataReader(DbConnectiondbcon,stringcmdText)
{
try
{
if(dbcon.State==ConnectionState.Closed)
{
dbcon.Open();
}
cmd.CommandText=Replace(cmdText);
DbDataReaderdr=cmd.ExecuteReader();
cmd.Parameters.Clear();
cmd.Dispose();
returndr;
}
catch
{
dbcon.Close();//发生异常在此处关闭,否则在调用显式处关闭
returnnull;
}
}
///<summary>
///判断记录是否存在
///</summary>
///<paramname="Sql"></param>
///<returns></returns>
publicboolExist(stringSql)
{
boolexist;
this.Open();
cmd.CommandText=Replace(Sql);
DbDataReaderdr=cmd.ExecuteReader();
if(dr.HasRows)
{
exist=true;//记录存在
}
else
{
exist=false;//记录不存在
}
dr.Close();
this.Close();
returnexist;
}
///<summary>
///执行SQL语句
///</summary>
///<paramname="sql"></param>
publicvoidExecSql(stringSql)
{
try
{
this.Open();
cmd.CommandText=Replace(Sql);
cmd.ExecuteNonQuery();
cmd.Dispose();
}
catch(Exceptionex)
{
throwex;
}
finally
{
this.Close();
}
}
///<summary>
///执行SQL语句,返回一个单值
///</summary>
///<paramname="sql"></param>
///<returns></returns>
publicstringReturnValue(stringSql)
{
objectreturnValue=string.Empty;
try
{
this.Open();
cmd.CommandText=Replace(Sql);
returnValue=cmd.ExecuteScalar();
if(returnValue==null)
{
returnValue=string.Empty;
}
}
catch(Exceptionex)
{
throwex;
}
finally
{
this.Close();
}
returnreturnValue.ToString();
}
///<summary>
///执行多条SQL语句并启用数据库事务
///</summary>
///<paramname="SQLStringList"></param>
publicboolExecSqlTran(List<String>SQLStringList)
{
this.Open();
DbTransactiontrans=conn.BeginTransaction();
cmd.Transaction=trans;
try
{
for(intn=0;n<SQLStringList.Count;n++)
{
cmd.CommandText=Replace(SQLStringList[n]);
cmd.ExecuteNonQuery();
}
trans.Commit();
returntrue;
}
catch
{
trans.Rollback();
returnfalse;
}
finally
{
this.Close();
}
}
下面说下两个存储过程,存储过程基本上分两种,返回结果集的存储过程和执行业务逻辑不返回结果集但却有返回值(如标志等),对于需要有返回值的存储过程,我个人趋向于用输出
参数代替返回值,因为都能达到一样的效果目的,而且输出参数可以有多个,也就可以根据需要能有多个所谓的“返回值”,所以我之前的开发中一直是用output参数来代替return参数。
Code
///<summary>
///执行存储过程并返回结果集
///</summary>
///<paramname="storedProcName">存储过程名</param>
///<returns>DataSet</returns>
publicDataSetRunProcedure(stringstoredProcName)
{
DataSetds=newDataSet();
try
{
this.Open();
cmd.CommandText=storedProcName;
cmd.CommandType=CommandType.StoredProcedure;
Adapter.SelectCommand=cmd;
//Adapter.SelectCommand.CommandTimeout=1200;//可以设置适当的超时时间(秒),避免选择时间段过大导致填充数据集超时
Adapter.Fill(ds);
}
catch(Exceptionex)
{
throwex;
}
finally
{
this.Close();
}
returnds;
}
///<summary>
///执行存储过程,方法不返回结果集
///</summary>
///<paramname="storedProcName"></param>
publicvoidRunVoidProcedure(stringstoredProcName)
{
cmd.CommandText=storedProcName;
cmd.CommandType=CommandType.StoredProcedure;
try
{
this.Open();
cmd.ExecuteNonQuery();
}
catch(Exceptionex)
{
throwex;
}
finally
{
this.Close();
}
}
下面说两个反射方法,测试之后为了方便调用,减少操作添加的,一个是把实体类的属性转换为参数,另一个是把从数据库取出的某条记录转换为实体类,这两个还是非常有用,尤其是在系统开发时调用比较方便,以前我是见到反射就绕道走的,这次算是第一次用反射,发现确实是很方便。如下:
Code
///<summary>
///将实体类的属性进行参数转换(ORACLE测试通不过,必须要求所有参数都包含在语句中才行)
///</summary>
///<paramname="model"></param>
///<paramname="ParaCollect"></param>
//publicvoidConvertToParameters(objectmodel,DbParameterCollectionParaCollect)
//{
//TypeT=model.GetType();
//PropertyInfo[]propert=T.GetProperties();
//for(inti=0;i<propert.Length;i++)
//{
//AddParam(propert[i].Name,propert[i].GetValue(model,null),ParaCollect);
//}
//}
///<summary>
///将实体类的属性进行参数转换
///</summary>
///<paramname="model"></param>
///<paramname="ParaCollect"></param>
publicvoidConvertToParameters(objectmodel,DbParameterCollectionParaCollect,List<string>fields)
{
TypeT=model.GetType();
PropertyInfo[]propert=T.GetProperties();
for(inti=0;i<propert.Length;i++)
{
if(fields.Contains(propert[i].Name))//检测必须参数化的实体属性
{
AddParam(propert[i].Name,propert[i].GetValue(model,null),ParaCollect);
}
}
}
///<summary>
///通过反射将取出的数据写入实体类(ORACLE测试通不过,需进行类型强制转换)
///</summary>
///<paramname="model"></param>
///<paramname="cmdText"></param>
//publicvoidGetModel(objectmodel,stringcmdText)
//{
//PropertyInfopropertyInfo;
//DbDataReaderdr=ExecuteDataReader(conn,cmdText);
//while(dr.Read())
//{
//for(inti=0;i<dr.FieldCount;i++)
//{
//propertyInfo=model.GetType().GetProperty(dr.GetName(i));
//if(propertyInfo!=null)
//{
//if(dr.GetValue(i)!=DBNull.Value)
//{
////Typet=dr.GetValue(i).GetType();
//propertyInfo.SetValue(model,dr.GetValue(i),null);
//}
//}
//}
//}
//dr.Close();
//conn.Close();
//}
///<summary>
///通过反射将数据绑定到实体对象,由于不同数据库对应于.NET的数据类型不一样
///需做强制类型转换
///</summary>
///<paramname="model"></param>
///<paramname="cmdText"></param>
publicvoidGetModel(objectmodel,stringcmdText)
{
PropertyInfopropertyInfo;
DbDataReaderdr=ExecuteDataReader(conn,cmdText);
object_value;
while(dr.Read())
{
for(inti=0;i<dr.FieldCount;i++)
{
propertyInfo=model.GetType().GetProperty(dr.GetName(i));
if(propertyInfo!=null&&dr.GetValue(i)!=DBNull.Value)
{
switch(propertyInfo.PropertyType.ToString())
{
case"System.String":
{
_value=Convert.ToString(dr.GetValue(i));//字符串是全球通用类型,也可以不用转换
propertyInfo.SetValue(model,_value,null);
}break;
case"System.Int32":
{
_value=Convert.ToInt32(dr.GetValue(i));
propertyInfo.SetValue(model,_value,null);
}break;
case"System.Single":
{
_value=Convert.ToSingle(dr.GetValue(i));
propertyInfo.SetValue(model,_value,null);
}break;
case"System.Decimal":
{
_value=Convert.ToDecimal(dr.GetValue(i));
propertyInfo.SetValue(model,_value,null);
}break;
case"System.Double":
{
_value=Convert.ToDouble(dr.GetValue(i));
propertyInfo.SetValue(model,_value,null);
}break;
case"":
{
_value=Convert.ToDateTime(dr.GetValue(i));
propertyInfo.SetValue(model,_value,null);
}break;
default:break;
}
}
}
}
dr.Close();
conn.Close();
}
从上面的注释掉的方法对比中可以看到为了兼容不同的数据库,必须要做额外的处理,比如类型转换,SQL SERVER的int 对应ORALCE的number,UserInfo的字段属性UserAge定义的是int类型,连接ORALCE时,.NET识别number类型为System.Decimal,把Decimal赋值给Int32当然是不行的,所以得做强制转换才行。还有一点要注意下,就是将数据绑定到实体对象时,由于ORACLE坚持大写标准和解析机制,如果属性名和字段名大小写不一致的话,propertyInfo = model.GetType().GetProperty(dr.GetName(i)) ,propertyInfo 始终是null值,比如SELECT UserName,UserAge FROM USER_TEST WHERE USERID=$USERID,SQL SERVER 执行的时候调试可以看到dr.GetName(0)是UserName,dr.GetName(1)是UserAge,ORACLE执行解析就变了,全是大写了,变成了USERNAE,USERAGE,这么一来和找不到UserInfo类的属性了,因为UserInfo类的属性是 UserName,和UserAge,C#语言变量也是区分大小写的嘛,当然就找不到了,所以propertyInfo就为null了,故在这里再次建议大家在数据库设计和程序字段属性设计时采用大写标准(如果不涉及多数据库当然也不需要这么做)。
最后说下测试调用代码,首先webconfig配置里面这样配置下,主要选取SQL SERVER和ORACLE做测试,毕竟这是.NET支持的两个典型数据库,要是把.NET所支持的所有书库都测试一遍,那测试量可不小了,呵呵。
<connectionStrings>
<addname="ConnStr"connectionString="uid=sa;pwd=peace;database=TEST;server=."providerName="System.Data.SqlClient"/>
<!--<addname="ConnStr"connectionString="server=.;datasource=peace;userid=cct;password=cct;enlist=true"providerName="System.Data.OracleClient"/>-->
</connectionStrings>
protectedvoidPage_Load(objectsender,EventArgse)
{
//测试DataReader,SQLSERVER和ORACLE都通过
//DataProviderFactoryfac=newDataProviderFactory();
//DbParameterCollectionParaCollect=fac.GetParmCollection();
//fac.AddParam("USERID",100,ParaCollect);
//DbDataReaderdr=fac.ExecuteDataReader(fac.conn,"SELECT*FROMUSER_TESTWHEREUSERID=$USERID");
//while(dr.Read())
//{
//stringa=dr[1].ToString();
//}
//fac.conn.Close();//在调用处显示关闭
//无参数DataSet测试SQLSERVER和ORACLE都通过
//DataTabledt=fac.ExecDataSet("SELECT*FROMUSER_TEST").Tables[0];
//带参数DataSet测试SQLSERVER和ORACLE都通过
//DbParameterCollectionParaCollect=fac.GetParmCollection();
//fac.AddParam("USERID",100,ParaCollect);
//fac.AddParam("USERNAME","局%",ParaCollect);//这里的参数名可以任意成其它,不一定非要和字段名相同(下同)
//DataTabledt=fac.ExecDataSet("SELECT*FROMUSER_TESTWHEREUSERNAMELIKE$USERNAME").Tables[0];
//DataTabledt=fac.ExecDataSet("SELECT*FROMUSER_TESTWHEREUSERID=$USERIDORUSERNAMELIKE$USERNAME").Tables[0];//多参数测试
//单值测试(带参数)SQLSERVER和ORACLE都通过
//DbParameterCollectionParaCollect=fac.GetParmCollection();
//fac.AddParam("USERID",100,ParaCollect);
//stringretValue=fac.ReturnValue("SELECTUSERNAMEFROMUSER_TESTWHEREUSERID=$USERID");
//带参存储过程测试返回结果集SQLSERVER和ORACLE都通过
//DbParameterCollectionParaCollect=fac.GetParmCollection();
//fac.AddParam("StartDate","2009-8-1",ParaCollect);
//fac.AddParam("EndDate","2009-8-21",ParaCollect);
//DataTabledt=fac.RunProcedure("USP_GetMixedReport").Tables[0];
//带参数测试存储过程的输出参数值和返回值,方法不返回结果集SQLSERVER通过
//intflag=0,sign=0,ret=0;
//DbParameterCollectionParaCollect=fac.GetParmCollection();
//fac.AddParam("USER_ACCOUNT",DbType.String,"admin",ParaCollect);
//fac.AddParam("USER_PWD",DbType.String,"68053af2923e00204c3ca7c6a3150cf7",ParaCollect);
//fac.AddParam("FLAG",DbType.Int32,"",ParaCollect);
//ParaCollect["@FLAG"].Direction=System.Data.ParameterDirection.Output;
//fac.AddParam("SIGN",DbType.Int32,"",ParaCollect);
//ParaCollect["@SIGN"].Direction=System.Data.ParameterDirection.Output;
//fac.AddParam("RetValue",DbType.String,"",ParaCollect);
//ParaCollect["@RetValue"].Direction=System.Data.ParameterDirection.ReturnValue;
//fac.RunVoidProcedure("SP_ValideLogin");
//flag=int.Parse(ParaCollect["@FLAG"].Value.ToString());
//sign=int.Parse(ParaCollect["@SIGN"].Value.ToString());
//ret=int.Parse(ParaCollect["@RetValue"].Value.ToString());//存储过程约定返回值必须是int型
//改进后带参数测试存储过程的输出参数值和返回值的测试SQLSERVER和ORACLE都通过
//intflag=0,sign=0,ret=0;
//DataProviderFactoryfac=newDataProviderFactory();
//DbParameterCollectionParaCollect=fac.GetParmCollection();
//fac.AddInputParam("USER_ACCOUNT","admin",ParaCollect);
//fac.AddInputParam("USER_PWD","68053af2923e00204c3ca7c6a3150cf7",ParaCollect);
//fac.AddOutputParam("FLAG",ParaCollect);
//fac.AddOutputParam("SIGN",ParaCollect);
//fac.AddReturnParam("RetValue",ParaCollect);
//fac.RunVoidProcedure("SP_ValideLogin");
//stringprefix=fac.retParaformat.Replace(":","");//Oracle存储过程参数前冒号移除掉
//flag=int.Parse(ParaCollect[string.Format(prefix,"FLAG")].Value.ToString());
//sign=int.Parse(ParaCollect[string.Format(prefix,"SIGN")].Value.ToString());
//ret=int.Parse(ParaCollect[string.Format(prefix,"RetValue")].Value.ToString());//存储过程约定返回值必须是int型
//调用存储过程测试SQLSERVER和ORACLE都通通过
//DataProviderFactoryfac=newDataProviderFactory();
//DbParameterCollectionParaCollect=fac.GetParmCollection();
//fac.AddInputParam("P_UserID",7,ParaCollect);
//fac.AddInputParam("P_UserName","peace",ParaCollect);
//fac.AddInputParam("P_UserAge",100,ParaCollect);
//fac.RunVoidProcedure("PROC_USER_TEST_ADD");
//多条提交事务处理测试SQLSERVER和ORACLE都通过
//List<string>SqlList=newList<string>();
//DataProviderFactoryfac=newDataProviderFactory();
//DbParameterCollectionParaCollect=fac.GetParmCollection();
//fac.AddParam("UserName","peaceli",ParaCollect);
//fac.AddParam("UserAge",150,ParaCollect);
//SqlList.Add("INSERTINTOUSER_TEST(UserName,UserAge)VALUES($UserName,$UserAge)");
//SqlList.Add("INSERTINTOUSER_TEST(UserName,UserAge)VALUES($UserName,$UserAge)");
//SqlList.Add("INSERTINTOUSER_TEST(UserName,UserAge)VALUES($UserName,$UserAge)");
//fac.ExecSqlTran(SqlList);
//插入操作参数测试(SQLSERVER)通过
//UserInfoui=newUserInfo();
//ui.UserName="hellopeace";
//ui.UserAge=100;
//Addinn(ui);
//插入操作参数测试(Oracle)通过
//UserInfoui=newUserInfo();
//ui.USERID=10;
//ui.USERNAME="hellopeace";
//ui.USERAGE=120;
//Addin(ui);
//插入操作反射参数转换测试SQLSERVER和ORACLE都通过
//UserInfoui=newUserInfo();
//ui.USERNAME="peaceli";
//ui.USERAGE=110;
//Add(ui);
//返回实体对象测试SQLSERVER和ORACLE都通过
UserInfoui=newUserInfo();
ui.USERID=1;
GetInfo(ui);
}
//privatevoidAddinn(UserInfoui)
//{
//DataProviderFactoryfac=newDataProviderFactory();
//DbParameterCollectionParaCollect=fac.GetParmCollection();
//fac.AddParam("@UserName",ui.UserName,ParaCollect);
//fac.AddParam("@UserAge",ui.UserAge,ParaCollect);
//fac.ExecSql("INSERTINTOUSER_TEST(UserName,UserAge)VALUES(@UserName,@UserAge)");
//}
privatevoidAddin(UserInfoui)
{
DataProviderFactoryfac=newDataProviderFactory();
DbParameterCollectionParaCollect=fac.GetParmCollection();
//fac.AddParam(":UserName",ui.UserName,ParaCollect);//给参数赋值时冒号可以不加,但有的版本可能必须加
//fac.AddParam(":UserAge",ui.UserAge,ParaCollect);
//fac.AddParam("UserID",ui.USERID,ParaCollect);//这行注释放开在ORACLE下同不过,ORACLE要求所全参数匹配,有多余参数就不行,这点有些变态
fac.AddParam("UserName",ui.USERNAME,ParaCollect);//SQLSERVER只要求用到的参数包含在参数集合里就行了,其它多余参数并不影响执行
fac.AddParam("UserAge",ui.USERAGE,ParaCollect);
fac.ExecSql("INSERTINTOUSER_TEST(UserName,UserAge)VALUES(:UserName,:UserAge)");
}
privatevoidAdd(UserInfoui)
{
DataProviderFactoryfac=newDataProviderFactory();
DbParameterCollectionParaCollect=fac.GetParmCollection();
string[]fields={"USERNAME","USERAGE"};//要求参数化的实体属性
List<string>ListFields=newList<string>(fields);
fac.ConvertToParameters(ui,ParaCollect,ListFields);//如果新增记录有很多参数的话,可能AddParam很多次,采用反射批量转换
fac.ExecSql("INSERTINTOUSER_TEST(USERNAME,USERAGE)VALUES($USERNAME,$USERAGE)");
}
privatevoidGetInfo(UserInfoui)
{
DataProviderFactoryfac=newDataProviderFactory();
DbParameterCollectionParaCollect=fac.GetParmCollection();
fac.AddParam("USERID",ui.USERID,ParaCollect);
fac.GetModel(ui,"SELECTUSERNAME,USERAGEFROMUSER_TESTWHEREUSERID=$USERID");
}
}
UserInfo类如下:
publicclassUserInfo
{
publicintUSERID{get;set;}
publicstringUSERNAME{get;set;}
publicintUSERAGE{get;set;}
}
测试到最后类属性改动过,统一改成了大写,再次建议大写标准(包括数据库设计),可以定义成USER_ID,USER_NAME,USER_AGE等,并与数据库字段名保持一致,这样有利于多数据库的
兼容。
结语:个人并不反对项目里单独用对应的xxhelper.cs,某个项目用SQLSERVER数据库,就用SqlHelper.csL类,ORACLE就用OracleHelper.cs类,这样来得更干脆快捷,基本上每个项目都是这对特定的数据库在开发,没必要搞成通用类,真要搞成通用类,要经过大量的实际测试,也许我最近有时寂寞空虚也无聊,突然想测试下同时也想改进下,呵呵,零零碎碎花了点时间测试了下,选取两个数据库测试了一遍,最终只需要改动config配置的数据库连接就可以了,真正达到了一套系统的无缝切换。里面有些可能还说的不够准确,可能也还有遗漏的地方,仅供参考吧!!!
访问类库的文件完整的贴一次,如下:
Code
//*****************************************************************************************************************
//*编写人:peace
//*EMAIL:peacechzh@126.com
//*开发日期:2009-10-21
//*修改人:
//*修改日期:
//*描述:数据库工厂访问类
//*更新描述:里面供调用执行的各方法可带参数执行,在外部指定参数名和参数值即可。
//*最终期望:支持.NET所支持的所有数据库并达到系统的无缝切换(尽情的忽悠吧O(∩_∩)O~)
//*****************************************************************************************************************
usingSystem;
usingSystem.Collections;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Text;
usingSystem.Data;
usingSystem.Data.Common;
usingSystem.Configuration;
usingSystem.Reflection;
namespaceDataProvider
{
publicclassDataProviderFactory
{
publicDbConnectionconn;//抽象类型
privateDbCommandcmd;//抽象类型
privateDbProviderFactoryprovider;
privateDbParameterPara;//不同数据库参数类型的抽象类型
privateDbDataAdapterAdapter;//对应不同数据库的数据适配器
Dictionary<Type,String>ParametersFormat;//不同数据库参数格式化类型
publicstringretParaformat=string.Empty;//最终返回的格式化标志,如@{0},:{0}
publicDataProviderFactory()
{
//从配置文件中取出标示数据库类型的字符串并通过ProviderName的不同支持不同类型的数据库
stringproviderName=ConfigurationManager.ConnectionStrings["ConnStr"].ProviderName;//也可以用索引,从1开始
//创建一个数据库对应的实例,使用该实例就可以创建对应的connection,command和adapater等等对象
provider=DbProviderFactories.GetFactory(providerName);
//创建具体的数据库连接类型和命令执行类型
conn=provider.CreateConnection();
conn.ConnectionString=ConfigurationManager.ConnectionStrings["ConnStr"].ConnectionString;
cmd=provider.CreateCommand();
cmd.Connection=conn;
//创建具体的参数类型
Para=provider.CreateParameter();
//创建具体的适配器类型
Adapter=provider.CreateDataAdapter();
//不同数据库参数前缀格式化
ParametersFormat=newDictionary<Type,String>();
ParametersFormat.Add(typeof(System.Data.SqlClient.SqlCommand),"@{0}");//因SQLSERVER只返回{0}没有@前缀,在此初始化处理
//返回格式化标志
retParaformat=GetParameterFormat(cmd);
}
///<summary>
///添加参数
///</summary>
///<paramname="ParaName">参数名称</param>
///<paramname="SqlType">参数数据类型</param>
///<paramname="ParaValue">参数值</param>
///<paramname="ParaCollect">参数对象的集合</param>
publicvoidAddParam(stringParaName,DbTypeSqlType,objectParaValue,DbParameterCollectionParaCollect)
{
//不允许将一个DbCommand对象的Parameters插入到另外一个DbCommand对象,那么多个参数的话可以加上下面一句判断
//如果已经存在至少一个对象时,再深层拷贝一个
if(ParaCollect.Count>=1)
{
Para=(DbParameter)((ICloneable)ParaCollect[0]).Clone();
}
Para.ParameterName=string.Format(retParaformat,ParaName);
Para.DbType=SqlType;
if(ParaValue==null)
{
Para.Value=string.Empty;//DBNull.Value;
}
else
{
Para.Value=ParaValue;
}
ParaCollect.Add(Para);
}
publicvoidAddParam(stringParaName,objectParaValue,DbParameterCollectionParaCollect)
{
if(ParaCollect.Count>=1)
{
Para=(DbParameter)((ICloneable)ParaCollect[0]).Clone();
}
Para.ParameterName=string.Format(retParaformat,ParaName);//将参数格式化为具体的数据库参数格式
if(ParaValue==null)
{
Para.Value=string.Empty;
}
else
{
Para.Value=ParaValue;
}
ParaCollect.Add(Para);
}
///<summary>
///存储过程输入参数
///</summary>
///<paramname="ParaName"></param>
///<paramname="ParaValue"></param>
///<paramname="ParaCollect"></param>
publicvoidAddInputParam(stringParaName,objectParaValue,DbParameterCollectionParaCollect)
{
if(ParaCollect.Count>=1)
{
Para=(DbParameter)((ICloneable)ParaCollect[0]).Clone();
}
Para.ParameterName=string.Format(retParaformat.Replace(":",""),ParaName);//ORACLE存储过程参数前没有冒号
if(ParaValue==null)
{
Para.Value=string.Empty;
}
else
{
Para.Value=ParaValue;
}
ParaCollect.Add(Para);
}
///<summary>
///存储过程输出参数
///</summary>
///<paramname="ParaName"></param>
///<paramname="ParaValue"></param>
///<paramname="ParaCollect"></param>
publicvoidAddOutputParam(stringParaName,DbParameterCollectionParaCollect)
{
if(ParaCollect.Count>=1)
{
Para=(DbParameter)((ICloneable)ParaCollect[0]).Clone();
}
Para.ParameterName=string.Format(retParaformat.Replace(":",""),ParaName);
Para.Value=string.Empty;
ParaCollect.Add(Para);
ParaCollect[Para.ParameterName].Direction=System.Data.ParameterDirection.Output;//指定该参数为输出参数
}
///<summary>
///存储过程返回值参数
///</summary>
///<paramname="ParaName"></param>
///<paramname="ParaValue"></param>
///<paramname="ParaCollect"></param>
publicvoidAddReturnParam(stringParaName,DbParameterCollectionParaCollect)
{
if(ParaCollect.Count>=1)
{
Para=(DbParameter)((ICloneable)ParaCollect[0]).Clone();
}
Para.ParameterName=string.Format(retParaformat.Replace(":",""),ParaName);
Para.Value=string.Empty;
ParaCollect.Add(Para);
ParaCollect[Para.ParameterName].Direction=System.Data.ParameterDirection.ReturnValue;//指定该参数为返回值参数
}
///<summary>
///抽象参数集合类型
///</summary>
///<returns></returns>
publicDbParameterCollectionGetParmCollection()
{
returncmd.Parameters;
}
///<summary>
///执行SQL并返回数据集
///</summary>
///<paramname="sql"></param>
///<returns></returns>
publicDataSetExecDataSet(stringSql)
{
DataSetds=newDataSet();
try
{
this.Open();
cmd.CommandText=Replace(Sql);
Adapter.SelectCommand=cmd;
Adapter.Fill(ds);
}
catch(Exceptionex)
{
throwex;
}
finally
{
this.Close();
}
returnds;
}
///<summary>
///执行SQL语句并返回DataReader对象
///</summary>
///<paramname="dbcon"></param>
///<paramname="cmdText"></param>
///<returns></returns>
publicDbDataReaderExecuteDataReader(DbConnectiondbcon,stringcmdText)
{
try
{
if(dbcon.State==ConnectionState.Closed)
{
dbcon.Open();
}
cmd.CommandText=Replace(cmdText);
DbDataReaderdr=cmd.ExecuteReader();
cmd.Parameters.Clear();
cmd.Dispose();
returndr;
}
catch
{
dbcon.Close();//发生异常在此处关闭,否则在调用显式处关闭
returnnull;
}
}
///<summary>
///判断记录是否存在
///</summary>
///<paramname="Sql"></param>
///<returns></returns>
publicboolExist(stringSql)
{
boolexist;
this.Open();
cmd.CommandText=Replace(Sql);
DbDataReaderdr=cmd.ExecuteReader();
if(dr.HasRows)
{
exist=true;//记录存在
}
else
{
exist=false;//记录不存在
}
dr.Close();
this.Close();
returnexist;
}
///<summary>
///执行SQL语句
///</summary>
///<paramname="sql"></param>
publicvoidExecSql(stringSql)
{
try
{
this.Open();
cmd.CommandText=Replace(Sql);
cmd.ExecuteNonQuery();
cmd.Dispose();
}
catch(Exceptionex)
{
throwex;
}
finally
{
this.Close();
}
}
///<summary>
///执行SQL语句,返回一个单值
///</summary>
///<paramname="sql"></param>
///<returns></returns>
publicstringReturnValue(stringSql)
{
objectreturnValue=string.Empty;
try
{
this.Open();
cmd.CommandText=Replace(Sql);
returnValue=cmd.ExecuteScalar();
if(returnValue==null)
{
returnValue=string.Empty;
}
}
catch(Exceptionex)
{
throwex;
}
finally
{
this.Close();
}
returnreturnValue.ToString();
}
///<summary>
///执行多条SQL语句并启用数据库事务
///</summary>
///<paramname="SQLStringList"></param>
publicboolExecSqlTran(List<String>SQLStringList)
{
this.Open();
DbTransactiontrans=conn.BeginTransaction();
cmd.Transaction=trans;
try
{
for(intn=0;n<SQLStringList.Count;n++)
{
cmd.CommandText=Replace(SQLStringList[n]);
cmd.ExecuteNonQuery();
}
trans.Commit();
returntrue;
}
catch
{
trans.Rollback();
returnfalse;
}
finally
{
this.Close();
}
}
///<summary>
///执行存储过程并返回结果集
///</summary>
///<paramname="storedProcName">存储过程名</param>
///<returns>DataSet</returns>
publicDataSetRunProcedure(stringstoredProcName)
{
DataSetds=newDataSet();
try
{
this.Open();
cmd.CommandText=storedProcName;
cmd.CommandType=CommandType.StoredProcedure;
Adapter.SelectCommand=cmd;
//Adapter.SelectCommand.CommandTimeout=1200;//可以设置适当的超时时间(秒),避免选择时间段过大导致填充数据集超时
Adapter.Fill(ds);
}
catch(Exceptionex)
{
throwex;
}
finally
{
this.Close();
}
returnds;
}
///<summary>
///执行存储过程,方法不返回结果集
///</summary>
///<paramname="storedProcName"></param>
publicvoidRunVoidProcedure(stringstoredProcName)
{
cmd.CommandText=storedProcName;
cmd.CommandType=CommandType.StoredProcedure;
try
{
this.Open();
cmd.ExecuteNonQuery();
}
catch(Exceptionex)
{
throwex;
}
finally
{
this.Close();
}
}
///<summary>
///将实体类的属性进行参数转换(ORACLE测试通不过,必须要求所有参数都包含在语句中才行)
///</summary>
///<paramname="model"></param>
///<paramname="ParaCollect"></param>
//publicvoidConvertToParameters(objectmodel,DbParameterCollectionParaCollect)
//{
//TypeT=model.GetType();
//PropertyInfo[]propert=T.GetProperties();
//for(inti=0;i<propert.Length;i++)
//{
//AddParam(propert[i].Name,propert[i].GetValue(model,null),ParaCollect);
//}
//}
///<summary>
///将实体类的属性进行参数转换
///</summary>
///<paramname="model"></param>
///<paramname="ParaCollect"></param>
publicvoidConvertToParameters(objectmodel,DbParameterCollectionParaCollect,List<string>fields)
{
TypeT=model.GetType();
PropertyInfo[]propert=T.GetProperties();
for(inti=0;i<propert.Length;i++)
{
if(fields.Contains(propert[i].Name))//检测必须参数化的实体属性
{
AddParam(propert[i].Name,propert[i].GetValue(model,null),ParaCollect);
}
}
}
///<summary>
///通过反射将取出的数据写入实体类(ORACLE测试通不过,需进行类型强制转换)
///</summary>
///<paramname="model"></param>
///<paramname="cmdText"></param>
//publicvoidGetModel(objectmodel,stringcmdText)
//{
//PropertyInfopropertyInfo;
//DbDataReaderdr=ExecuteDataReader(conn,cmdText);
//while(dr.Read())
//{
//for(inti=0;i<dr.FieldCount;i++)
//{
//propertyInfo=model.GetType().GetProperty(dr.GetName(i));
//if(propertyInfo!=null)
//{
//if(dr.GetValue(i)!=DBNull.Value)
//{
////Typet=dr.GetValue(i).GetType();
//propertyInfo.SetValue(model,dr.GetValue(i),null);
//}
//}
//}
//}
//dr.Close();
//conn.Close();
//}
///<summary>
///通过反射将数据绑定到实体对象,由于不同数据库对应于.NET的数据类型不一样
///需做强制类型转换
///</summary>
///<paramname="model"></param>
///<paramname="cmdText"></param>
publicvoidGetModel(objectmodel,stringcmdText)
{
PropertyInfopropertyInfo;
DbDataReaderdr=ExecuteDataReader(conn,cmdText);
object_value;
while(dr.Read())
{
for(inti=0;i<dr.FieldCount;i++)
{
propertyInfo=model.GetType().GetProperty(dr.GetName(i));
if(propertyInfo!=null&&dr.GetValue(i)!=DBNull.Value)
{
switch(propertyInfo.PropertyType.ToString())
{
case"System.String":
{
_value=Convert.ToString(dr.GetValue(i));//字符串是全球通用类型,也可以不用转换
propertyInfo.SetValue(model,_value,null);
}break;
case"System.Int32":
{
_value=Convert.ToInt32(dr.GetValue(i));
propertyInfo.SetValue(model,_value,null);
}break;
case"System.Single":
{
_value=Convert.ToSingle(dr.GetValue(i));
propertyInfo.SetValue(model,_value,null);
}break;
case"System.Decimal":
{
_value=Convert.ToDecimal(dr.GetValue(i));
propertyInfo.SetValue(model,_value,null);
}break;
case"System.Double":
{
_value=Convert.ToDouble(dr.GetValue(i));
propertyInfo.SetValue(model,_value,null);
}break;
case"":
{
_value=Convert.ToDateTime(dr.GetValue(i));
propertyInfo.SetValue(model,_value,null);
}break;
default:break;
}
}
}
}
dr.Close();
conn.Close();
}
///<summary>
///根据不同的数据库命令对象返回该类型数据库参数的前缀格式化字符串
///</summary>
///<paramname="command"></param>
///<returns></returns>
privatestringGetParameterFormat(DbCommandcommand)
{
if(!ParametersFormat.ContainsKey(command.GetType()))
{
this.Open();//读取参数前缀时需打开数据库连接
ParametersFormat.Add(
command.GetType(),
command.Connection.GetSchema("DataSourceInformation")
.Rows[0]["ParameterMarkerFormat"].ToString());
//conn.Close();在真正执行语句的时候去关闭,避免重复打开
}
returnParametersFormat[command.GetType()];
}
privatevoidOpen()
{
if(conn.State==ConnectionState.Closed)
{
conn.Open();
}
}
privatevoidClose()
{
if(conn.State==ConnectionState.Open)
{
conn.Close();
}
}
///<summary>
///替换DML语句里的参数前缀
///</summary>
///<paramname="str"></param>
///<returns></returns>
publicstringReplace(stringstr)
{
returnstr.Replace("$",retParaformat.Substring(0,1));
}
}
}
文件下载:PeaceHelper.cs
优质内容筛选与推荐>>
1、分析函数:scott/tiger下的emp表2、AtCoder Beginner Contest 088 D Grid Repainting3、影院售票系统(未完成)4、浏览器桌面提醒,适用于网站“新消息提醒”5、spring基础——DI(五)
长按二维码向我转账
受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。
阅读
好看
已推荐到看一看
你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
取消
分享想法到看一看
确定
最多200字,当前共字
微信扫一扫
关注该公众号