提前说明:正如网友反映的一样,为了不至于产生明显的误导,特别加了此首段说明
SQLHelper,几乎是每个过来者必经的阶段,写好一个SQLHelper是非常重要的一环,所以希望年轻的来者,要多加实践,别只看不动手,哪怕照着写一写,也是相当的有益。
对于本框架系列,希望年轻来者在掌握使用的同时,动手照着系列文章写一写,如果照着写出来的,相信成长不是一点半点的;别光看不练,最后只能忽悠却动不了手。
这篇文章很不好写,我在电脑前思索了一天,也不知怎么下手。
关于SQLHelper的文章遍地都是,写的不咋的随时被拍砖,不写吧,本系列又不完整,所以,买了个保险之后,低调点写了。
从哪写起呢?直接把整个SQLHelper类复制一下,文章就算写完了?好像其它遍地都是的文章都差不多是这个样子的。
在还没写完这篇时,曾经有那么个热心人士反编绎过我的框架,还洒了点代码出来了,提前爆光了一下:
详见:
1:CYQ.Data 轻量数据层之路 华丽升级 V1.3出世(五)
2:CYQ.Data 轻量数据层之路 应用示例二 在线聊天(六)
本文停了一天没动了,现在重新执笔动手了,想了想,于是在博客园搜了一下,看了第一页搜出来的10篇SQLHelper相关代码,
简略看了一眼,发现还是鄙人的简洁友好的多,于是,继续写下来了:
其实我们要的SQLHelper很简单,只要能执行下sql语句和存储过程,也就这个样了,至于事务,这里先放一边了。
接着一步一脚印:
1:我们新增加一个SQHelper类,由于本类并不对外开放,所以我们不改修饰符为public,默认就好了
///
<summary>
///
SQLHelper by 路过秋天
///
</summary>
class
SQLHelper
{
}
2:由于我们不做成静态方法调用方式,所以我们需要实例化,添加两个构造函数
///
<summary>
///
SQLHelper by 路过秋天
///
</summary>
class
SQLHelper
{
///
<summary>
///
默认配置连接字符串名:Conn
///
</summary>
public
SQLHelper()
{
}
///
<summary>
///
可以传链接字符串
///
</summary>
public
SQLHelper(
string
conn)
{
}
}
3:既然要实例化才调用,那我们只需要一个Command和一个链接就可以了,所以,我们把它们拿到外面定义成全局变量
class
SQLHelper
{
private
SqlCommand com
=
new
SqlCommand();
private
SqlConnection _con
=
null
;
///
<summary>
///
默认配置连接字符串名:Conn
///
</summary>
public
SQLHelper()
{
}
///
<summary>
///
可以传链接字符串
///
</summary>
public
SQLHelper(
string
conn)
{
}
}
上面没有直接new 出SqlConnection,是因为它和链接字符串相关,留到构造函数里初始化了。
4:实现构造函数,初始化SqlConnection
public
SQLHelper()
{
if
(ConfigurationManager.ConnectionStrings[
"
Conn
"
]
!=
null
)
{
_con
=
new
SqlConnection(ConfigurationManager.ConnectionStrings[
"
Conn
"
].ConnectionString);
com.Connection
=
_con;
}
}
public
SQLHelper(
string
conn)
{
if
(conn.Length
<
25
)
{
conn
=
ConfigurationManager.ConnectionStrings[conn].ConnectionString;
}
_con
=
new
SqlConnection(conn);
com.Connection
=
_con;
}
默认用webconfig配置Conn,同时上面根据传入的长度,来判断是从配置文件传入,还是直接的链接字符串!
5:我们增加一个全局的成员属性,一个是否记录异常,如果不记录则会抛出异常
public
bool
WriteLog
=
true
;
6:我这里另开一个Log类,来处理异常,先留下一个静态空方法,回头处理
class
Log
{
public
static
void
WriteLog(
string
message)
{
//
...待实现...
}
}
7:要执行SQL语句或存储过程,免不了要打开和关闭链接,这里封装成方法,加try
private
void
OpenCon()
{
try
{
if
(_con.State
==
ConnectionState.Closed)
{
_con.Open();
}
}
catch
(SqlException err)
{
if
(WriteLog)
{
Log.WriteLog(err.Message);
}
}
}
private
void
CloseCon()
{
try
{
if
(_con.State
==
ConnectionState.Open)
{
_con.Close();
}
}
catch
(SqlException err)
{
if
(WriteLog)
{
Log.WriteLog(err.Message);
}
}
}
我们在打开和关闭异时,调用了日志记录功能。
8:我们继承IDisposable接口,完成资源释放
class
SQLHelper:IDisposable
{
//
...省略N行...
#region
IDisposable 成员
public
void
Dispose()
{
if
(_con
!=
null
)
{
CloseCon();
_con
=
null
;
}
if
(com
!=
null
)
{
com
=
null
;
}
}
#endregion
}
9:我们封装一下SqlCommand的几个执行返回
internal
int
ExeNonQuery(
string
procName,
bool
isProc)
{
//
更新删除操作,返回受影响行数
}
internal
object
ExeScalar(
string
procName, bool isProc)
{
//
返回首行首列的单个记录
}
internal
SqlDataReader ExeDataReader(
string
procName,
bool
isProc)
{
//
返回读取流
}
internal
DataTable ExeDataTable(
string
procName,
bool
isProc)
{
//
返回DataTable,本框架没用到,因为有了MDataTable
}
由于存储过程和单独的sql语句混在一起,我加了一个函数来处理这些共同的事情:
private
void
SetCommandText(
string
commandText,
bool
isProc)
{
com.CommandText
=
commandText;
com.CommandType
=
isProc
?
CommandType.StoredProcedure : CommandType.Text;
if
(
!
com.Parameters.Contains(
"
ReturnValue
"
))
{
com.Parameters.Add(
"
ReturnValue
"
, SqlDbType.Int).Direction
=
ParameterDirection.ReturnValue;
}
}
后面附加了一个常用的返回值参数ReturnValue。
10:实现封装的几个方法,异常则记录日志/抛出
internal
int
ExeNonQuery(
string
procName,
bool
isProc)
{
//
更新删除操作,返回受影响行数
SetCommandText(procName, isProc);
int
rowCount
=
1
;
try
{
OpenCon();
com.ExecuteNonQuery();
}
catch
(SqlException err)
{
rowCount
=
0
;
if
(WriteLog)
{
Log.WriteLog(err.Message);
}
}
return
rowCount;
}
internal
object
ExeScalar(
string
procName, bool isProc)
{
//
返回首行首列的单个记录
SetCommandText(procName, isProc);
object
returnValue
=
null
;
try
{
OpenCon();
returnValue
=
com.ExecuteScalar();
}
catch
(SqlException err)
{
if
(WriteLog)
{
Log.WriteLog(err.Message);
}
}
return
returnValue;
}
internal
SqlDataReader ExeDataReader(
string
procName,
bool
isProc)
{
//
返回读取流
SetCommandText(procName, isProc);
SqlDataReader sdr
=
null
;
try
{
OpenCon();
sdr
=
com.ExecuteReader(CommandBehavior.CloseConnection);
if
(sdr
!=
null
&&
!
sdr.HasRows)
{
sdr.Close();
sdr
=
null
;
}
}
catch
(SqlException err)
{
if
(WriteLog)
{
Log.WriteLog(err.Message);
}
}
return
sdr;
}
internal
DataTable ExeDataTable(
string
procName,
bool
isProc)
{
//
返回DataTable,本框架没用到,因为有了MDataTable
SetCommandText(procName, isProc);
SqlDataAdapter sdr
=
new
SqlDataAdapter(com);
DataTable dataTable
=
new
DataTable();
try
{
OpenCon();
sdr.Fill(dataTable);
}
catch
(SqlException err)
{
if
(WriteLog)
{
Log.WriteLog(err.Message);
}
}
finally
{
sdr.Dispose();
}
return
dataTable;
}
11:参数方法,无论是存储过程,还是传参型的sql语句,都要用到
A:参数增加:
internal
void
AddParameters(
string
parameterName,
object
value)
{
if
(
!
com.Parameters.Contains(parameterName))
{
com.Parameters.AddWithValue(parameterName, value);
}
}
internal
void
AddParameters(
string
parameterName,
object
value, SqlDbType sqlDbType)
{
if
(
!
com.Parameters.Contains(parameterName))
{
com.Parameters.Add(parameterName, sqlDbType).Value
=
value;
}
}
B:参数清除:
internal
void
ClearParameters()
{
if
(com
!=
null
&&
com.Parameters
!=
null
)
{
com.Parameters.Clear();
}
}
12:返回值属性,一般用于返回记录总数
private
int
returnValue;
public
int
ReturnValue
{
get
{
if
(com
!=
null
&&
com.Parameters
!=
null
)
{
int
.TryParse(Convert.ToString(com.Parameters[
"
ReturnValue
"
].Value),
out
returnValue);
}
return
returnValue;
}
set
{ returnValue
=
value; }
}
就此,SQLHelper 就算写完了,余下要处理一下日志记录与异常抛出。
13:Log类异常日志记录与抛出
class
Log
{
public
static
void
WriteLog(
string
message)
{
if
(IsCanWrite())
{
InsertLogToData(message);
}
else
{
throw
new
Exception(message);
}
}
private
static
bool
IsCanWrite()
{
//
从配置文件取
}
private
static
void
InsertLogToData(
string
message)
{
//
错误日志入库,可以自定义写文本或入数据库
}
}
判断是一下是否设置为可写日志,如果是则写,否则抛异常。
14:实现IsCanWrite方法
private
static
bool
IsCanWrite()
{
bool
IsCanWriteLog;
bool
.TryParse(Convert.ToString(ConfigurationManager.AppSettings[
"
IsWriteLog
"
]),
out
IsCanWriteLog);
return
IsCanWriteLog;
}
从配置文件里取配置,如果配置为true,就记录日志,否则抛出异常。
插曲:写到这里,博客园又挂了,好在有预感之前保存了一下文章:上图:
十几分钟后,正常了!!!
15:实现InsertLogToData方法,将错误异常入库
private
static
void
InsertLogToData(
string
message)
{
//
错误日志入库,可以自定义写文本或入数据库
if
(ConfigurationManager.ConnectionStrings[
"
LogConn
"
]
==
null
)
{
return
;
}
string
pageUrl
=
System.Web.HttpContext.Current.Request.Url.ToString();
SQLHelper helper
=
new
SQLHelper(
"
LogConn
"
);
helper.WriteLog
=
false
;
//
再产生错误就不写日志了,不能产生死循环
try
{
helper.AddParameters(
"
@PageUrl
"
, pageUrl, System.Data.SqlDbType.NVarChar);
helper.AddParameters(
"
@ErrorMessage
"
, message, System.Data.SqlDbType.NVarChar);
helper.ExeNonQuery(
"
insert into ErrorLogs(PageUrl,ErrorMessage) values(@PageUrl,@ErrorMessage)
"
,
false
);
helper.Dispose();
}
catch
{
//
啥也不做了
}
}
这里只是对ErrorLogs表进行插入数据操作,将当前发生错误的Url地址及错误信息插入表中。
同时也演示了一把SQLHelper的用法。当然你可以修改成自己需要的日志记录方法。
至此,想了一天,终于把这个SQLHelper类给写完了,虽然此类在本框架中不对外开放使用,不过有心的读者仍可以独立出去使用。
OK,本节也就到此结束了。欢迎读者留言讨论或提出建议!
后语:从你学会写或用SQLHelper那时起,你还会去界面写一堆的类似这样的代码吗?
SqlConnection con
=
new
SqlConnection(
"
server=.;database=CYQ;uid=sa;pwd=123456
"
);
SqlCommand com
=
new
SqlCommand();
com.Connection
=
con;
com.CommandType
=
CommandType.Text;
com.CommandText
=
"
SELECT * FROM CYQTABLE WHERE ID=888
"
;
con.Open();
SqlDataReader sdr
=
com.ExecuteReader();
if
(sdr
!=
null
)
{
while
(sdr.HasRows)
{
sdr.Read();
//
...循环读...
}
sdr.Close();
}
con.Close();
难了吧,回头已太难了,不管是基于认知度的提升还是开发效率上,你都基本不回头了吧。
如果有一天
你又认知了微软的ADO.NET Entity Framework或是NHibernate或其它框架。
又或者有一天
你原创框架了
又或是使用本框架了
你还会回头用那个曾经过的SQLHelper么
只能说,一切回头太难。
备注:完整框架源码会在本系列结束之后另开章节发布,暂时望勿激动,学习思想才是重要的。