一、前言
相信许多人都百度过:“.net 数据库访问类”。然后就出来一大堆SqlHelper。我也用过这些SqlHelper,也自己写过,一堆静态方法,开始使用起来感觉很不错,它们也确实在很多时候可以很好的工作。ADO.NET已经封装很好了,我们很容易就可以实现自己的数据库访问类。
很久前,忘记在哪里看到过了,有一个朋友写了一篇【如何做一个好用的数据库访问类】(有兴趣的朋友仍然可以搜索到),这篇文章确实写得很好,作者很详细的讲解了如何设计一个好的数据库访问类;所谓“好“是指:轻量、易用、通用、高效。
其实代码是很久前就实现了,只是现在才总结记录,希望可以分享一下学习的过程。ok,在开始前先来看几个ADO.NET常见的面试题:
1. ADO.NET 5个核心对象是哪5个?
2. 与ADO.NET 相关对象中,哪些可以用于数据绑定?
3. DataSet与DataReader有什么区别?分别适用在什么情况?
二、需求
这是一个简单的、基于ADO.NET的数据库访问类,它最起码要具备以下特点:
1. 支持多种数据库
搞.net的视乎有一个固定的思维:数据库就是用sql server。额,只是很多用sql server,但不是全部,也有很多用 my sql 等的。我们并不能限制一定用什么数据库。
2. 支持多个数据库
有时候我们的应用程序会用到多个数据库,并且这些数据库还不是部署在同一台服务器上的。
3. 简单
满足常见的操作。
4. 可扩展
可以随时增加新的方法;对于具体的数据源,也可以有特有的操作。
三、主要说明
3.1 使用DbProviderFactory
既然要支持多种数据库,那么我们之前常写的SqlConnection、SqlCommand 就都不能用了,因为它们是针对sql server 数据源的。如果换成 my sql 就是 MySqlConnection, Oracle 就是 OracleConnection 了。
既然有那么多种Connection,很多朋友可能会想到通过设计模式来处理,例如定义一个父类(或接口),然后各种类型的数据库继承它,然后再通过一个工厂,来创建所需要的对象;以后要增加哪种类型的数据库就再增加一个对应的类就可以了。大概是像下面这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
public abstract class DBHelper
{ public abstract void Open( string key){}
public abstract int ExecuteNonQuery() { }
public abstract object ExecuteScalar() { }
public abstract DataSet GetDataSet() { }
} public class SqlHelper : DBHelper
{ const char _prefix = '@' ;
//实现抽象方法...
} public class MySqlHelper : DBHelper
{ const char _prefix = "@" ;
//实现抽象方法...
} public class OracleHelper : DBHelper
{ const char _prefix = ":" ;
//实现抽象方法...
} public class DBFactory
{ public static DBHelper GetDBHelper()
{
//根据条件返回DBHelper
}
} |
这样实现已经比用SqlXXX好很多了,这也是我之前写过的一种方式。但它仍然不够灵活,并且实现起来就会发现很多代码都是类似的,这就与我们上面的简单的需求相违背了。
通过上面的分析,我们知道用工厂模式可以解决我们的问题,但这不用我们自己实现,.net 早就提供这样的工厂:DbProviderFactory。由名称可以指定DbProviderFactory就是数据源提供程序工厂,负责创建具体的数据源提供程序。它根据 ProviderName就可以创建对应数据源的访问对象了。这样我们的实现也由具体变成抽象了,具体的SqlConection变成了抽象的DbConnection。
什么是 ProviderName? 在配置 web.config 的connectionStrings 时,就会有一个 providerNmae 属性,例如sql server就是 ”System.Data.SqlClient“,这个名称空间就是对应的数据源提供程序。
3.2 参数问题
不同数据库参数查询的格式可能不一样,例如 sql server/my sql 支持“@变量” 形式,而 oracle 支持“:变量”的形式。像上面的父类的写法,子类就必须定义自己的参数前缀。但这些用了DbProviderFactory后也不是问题了。
3.3 using 问题
我们都知道using是c#的语法糖,其实编译后就是 try-catch-finaly;uisng写起来比较优雅,而且在有异常的时候会自动调用对象的Disponse方法,避免有些人忘记调用。所以嵌套的 using,编译后就是嵌套的try-catch-finaly,但其实只要我们注意在抛异常的时候释放资源,一个try-catch-finaly即可。
3.4 DbDataReader 问题
实际项目中,我们更多的是使用DbDataReader而非DataSet/DataTable,而 DbDataReader需要自己逐行读取,这在每个调用的地方都这样写是很麻烦的,怎么解决?委托,又是它!
说到委托还有一个小小的建议,有些人喜欢自己去定义委托,但其实.net已经内置了3种委托:Func、Action、Predicate,并且提供了多个重载版本,应该优先考虑使用这些委托,在不满足的情况下,再去自定义。
3.5 在分层架构里的角色
为 DAL 层提供数据访问服务,由 DAL 直接调用;不涉及sql语句拼接、日志记录等。
四、例子
假设要调用一个 P_GetFriends存储过程,接收一个id参数,返回一个好友列表。如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public List<Friend> GetFriends( int id)
{ try
{
DBHelper helper = new DBHelper( "dbConnectionKey" );
DbParameter[] parameters = new DbParameter[]
{
helper.CreateDbParameter( "id" ,id)
};
return helper.ExecuteReader(CommandType.StoredProcedure, "P_GetFriends" , parameters,
reader =>
{
return new Friend()
{
ID = reader.GetInt32(reader.GetOrdinal( "ID" )),
Name = reader.GetString(reader.GetOrdinal( "Name" ))
};
});
}
catch
{
throw ;
}
} |
附源代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
|
public class DBHelper
{ #region 属性
/// <summary>
/// 链接字符串
/// </summary>
private string conStr;
/// <summary>
/// DB工厂
/// </summary>
private DbProviderFactory provider;
#endregion
#region 构造函数
/// <summary>
/// 构造函数
/// </summary>
/// <param name="key">链接字符串键</param>
public DBHelper( string key)
{
if ( string .IsNullOrEmpty(key))
{
throw new ArgumentNullException( "key" );
}
ConnectionStringSettings css = WebConfigurationManager.ConnectionStrings[key];
if (css == null )
{
throw new InvalidOperationException( "未找到指定的链接字符串!" );
}
this .conStr = css.ConnectionString;
this .provider = DbProviderFactories.GetFactory(css.ProviderName);
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="conStr">链接字符串</param>
/// <param name="providerStr">数据源提供程序</param>
public DBHelper( string conStr, string providerStr)
{
if ( string .IsNullOrEmpty(conStr))
{
throw new ArgumentNullException( "conStr" );
}
if ( string .IsNullOrEmpty(providerStr))
{
throw new ArgumentNullException( "providerStr" );
}
this .provider = DbProviderFactories.GetFactory(providerStr);
this .conStr = conStr;
}
#endregion
#region 外部方法
/// <summary>
/// 执行命令,返回受影响行数
/// </summary>
/// <param name="commandType">命令类型</param>
/// <param name="sql">sql语句或存储过程名称</param>
/// <param name="parameters">参数数组</param>
/// <returns>受影响行数,失败返回-1</returns>
public virtual int ExecuteNonQuery(CommandType commandType, string sqlOrProcName, IEnumerable<DbParameter> parameters)
{
DbConnection con = CreateConnection();
DbCommand cmd = CreateCommand(con, commandType, sqlOrProcName, parameters);
try
{
con.Open();
int row = cmd.ExecuteNonQuery();
return row;
}
catch
{
throw ;
}
finally
{
cmd.Dispose();
con.Close();
}
}
/// <summary>
/// 执行命令,返回第一行第一列对象
/// </summary>
/// <param name="commandType">命令类型</param>
/// <param name="sql">sql语句或存储过程名称</param>
/// <param name="parameters">参数数组</param>
/// <returns>执行结果</returns>
public virtual object ExecuteScalar(CommandType commandType, string sqlOrProcName, IEnumerable<DbParameter> parameters)
{
DbConnection con = CreateConnection();
DbCommand cmd = CreateCommand(con, commandType, sqlOrProcName, parameters);
try
{
con.Open();
object obj = cmd.ExecuteScalar();
return obj;
}
catch
{
throw ;
}
finally
{
cmd.Dispose();
con.Close();
}
}
/// <summary>
/// 执行命令返回DataSet
/// </summary>
/// <param name="commandType">命令类型</param>
/// <param name="sql">sql语句或存储过程名称</param>
/// <param name="parameters">参数数组</param>
/// <returns>DataSet</returns>
public virtual DataSet GetDataSet(CommandType commandType, string sqlOrProcName, IEnumerable<DbParameter> parameters)
{
DbConnection con = CreateConnection();
DbCommand cmd = CreateCommand(con, commandType, sqlOrProcName, parameters);
DataSet set = new DataSet();
DbDataAdapter adapter = this .provider.CreateDataAdapter();
try
{
con.Open();
adapter.SelectCommand = cmd;
adapter.Fill( set );
return set ;
}
catch
{
throw ;
}
finally
{
adapter.Dispose();
cmd.Dispose();
con.Close();
}
}
/// <summary>
/// 执行命令返回DbDataReader
/// </summary>
/// <param name="commandType">命令类型</param>
/// <param name="sql">sql语句或存储过程名称</param>
/// <param name="parameters">参数数组</param>
/// <param name="action">委托</param>
/// <returns>对象列表</returns>
public virtual List<T> ExecuteReader<T>(CommandType commandType, string sqlOrProcName, IEnumerable<DbParameter> parameters,
Func<DbDataReader, T> action)
{
DbConnection con = CreateConnection();
DbCommand cmd = CreateCommand(con, commandType, sqlOrProcName, parameters);
List<T> result = new List<T>();
try
{
con.Open();
DbDataReader reader = cmd.ExecuteReader();
try
{
while (reader.Read())
{
var item = action(reader);
result.Add(item);
}
return result;
}
catch
{
throw ;
}
finally
{
reader.Dispose();
}
}
catch
{
throw ;
}
finally
{
cmd.Dispose();
con.Close();
}
}
/// <summary>
/// 批量执行sql语句
/// </summary>
/// <param name="sqlList">sql语句集合</param>
/// <param name="paramList">参数数组集合</param>
/// <returns>执行成功或失败</returns>
public virtual bool ExecuteSqlBatchByTrans(IEnumerable< string > sqlList, IEnumerable<List<DbParameter>> paramList)
{
DbConnection con = CreateConnection();
DbCommand cmd = CreateCommand(con, CommandType.Text);
try
{
con.Open();
DbTransaction trans = con.BeginTransaction();
cmd.Transaction = trans;
try
{
int length = sqlList.Count();
IEnumerable<DbParameter> parameters = null ;
for ( int i = 0; i < length; i++)
{
cmd.CommandText = sqlList.ElementAt< string >(i);
cmd.Parameters.Clear();
parameters = paramList.ElementAt<List<DbParameter>>(i);
foreach (DbParameter pm in parameters)
{
cmd.Parameters.Add(pm);
}
cmd.ExecuteNonQuery();
}
trans.Commit();
return true ;
}
catch
{
trans.Rollback();
throw ;
}
finally
{
trans.Dispose();
}
}
catch
{
throw ;
}
finally
{
cmd.Dispose();
con.Close();
}
}
#endregion
#region CreateDbParameter
public DbParameter CreateDbParameter( string name, object value)
{
DbParameter parameter = this .provider.CreateParameter();
parameter.ParameterName = name;
parameter.Value = value;
return parameter;
}
public DbParameter CreateDbParameter( string name, object value, ParameterDirection direction)
{
DbParameter parameter = this .provider.CreateParameter();
parameter.ParameterName = name;
parameter.Value = value;
parameter.Direction = direction;
return parameter;
}
public DbParameter CreateDbParameter( string name, object value, int size)
{
DbParameter parameter = this .provider.CreateParameter();
parameter.ParameterName = name;
parameter.Value = value;
parameter.Size = size;
return parameter;
}
public DbParameter CreateDbParameter( string name, object value, int size, DbType type)
{
DbParameter parameter = this .provider.CreateParameter();
parameter.ParameterName = name;
parameter.Value = value;
parameter.Size = size;
parameter.DbType = type;
return parameter;
}
public DbParameter CreateDbParameter( string name, object value, int size, DbType type, ParameterDirection direction)
{
DbParameter parameter = this .provider.CreateParameter();
parameter.ParameterName = name;
parameter.Value = value;
parameter.Size = size;
parameter.DbType = type;
parameter.Direction = direction;
return parameter;
}
#endregion
#region 私有方法
/// <summary>
/// 获取链接实例
/// </summary>
/// <returns>链接实例</returns>
private DbConnection CreateConnection()
{
DbConnection con = this .provider.CreateConnection();
con.ConnectionString = this .conStr;
return con;
}
/// <summary>
/// 获取命令实例
/// </summary>
/// <param name="con">链接实例</param>
/// <param name="commandType">命令类型</param>
/// <param name="sqlOrProcName">sql语句或存储过程名称</param>
/// <returns>命令实例</returns>
private DbCommand CreateCommand(DbConnection con, CommandType commandType, string sqlOrProcName, IEnumerable<DbParameter> parameters)
{
DbCommand cmd = InitCommand(con, commandType, parameters);
cmd.CommandText = sqlOrProcName;
return cmd;
}
/// <summary>
/// 获取命令实例
/// </summary>
/// <param name="con">链接实例</param>
/// <param name="commandType">命令类型</param>
/// <returns>命令实例</returns>
private DbCommand CreateCommand(DbConnection con, CommandType commandType)
{
return InitCommand(con, commandType, null );
}
/// <summary>
/// 初始化命令
/// </summary>
/// <param name="commandType">命令类型</param>
/// <param name="parameters">参数集合</param>
/// <returns></returns>
private DbCommand InitCommand(DbConnection con, CommandType commandType, IEnumerable<DbParameter> parameters)
{
DbCommand cmd = con.CreateCommand();
cmd.CommandType = commandType;
if (parameters != null )
{
foreach (DbParameter pm in parameters)
{
cmd.Parameters.Add(pm);
}
}
return cmd;
}
#endregion
} |
我也来写:数据库访问类DBHelper(转)的更多相关文章
-
我也来写:数据库访问类DBHelper
一.前言 相信许多人都百度过:“.net 数据库访问类”.然后就出来一大堆SqlHelper.我也用过这些SqlHelper,也自己写过,一堆静态方法,开始使用起来感觉很不错,它们也确实在很多时候可以 ...
-
C#.NET数据库访问类DBHelper
这是一个与C# .NET通用的数据库访问类,包含了工厂模式.事务处理等安全机制. 调用方式: DBHelper db = new DBHelper(); DbCommand cmd = db.GetS ...
-
学习实践:使用模式,原则实现一个C++数据库访问类
一.概述 在我参与的多个项目中,大家使用libMySQL操作MySQL数据库,而且是源码即复用,在多个项目中有多套相同或相似的源码,这样的复用方式给开发带来了不变,而且libMySQL的使用比较麻烦, ...
-
DataAccess通用数据库访问类,简单易用,功能强悍
以下是我编写的DataAccess通用数据库访问类,简单易用,支持:内联式创建多个参数.支持多事务提交.支持参数复用.支持更换数据库类型,希望能帮到大家,若需支持查出来后转换成实体,可以自行扩展dat ...
-
一个通用数据库访问类(C#,SqlClient)
本文转自:http://www.7139.com/jsxy/cxsj/c/200607/114291.html使用ADO.NET时,每次数据库操作都要设置connection属性.建立connecti ...
-
关于PHP建立数据库访问类的封装以及操作php单例模式连接数据库封装类
建立数据库访问类的封装 <?php class DBDA { public $host = "localhost"; //服务器地址 public $ui ...
-
一个C#的XML数据库访问类
原文地址:http://hankjin.blog.163.com/blog/static/33731937200942915452244/ 程序中不可避免的要用到配置文件或数据,对于数据量比较小的程序 ...
-
通用数据库帮助类DBHelper(含log日志信息实时记录)
项目需要,需要一个通用的数据库操作类,增删改查.事务.存储过程.日志记录都要有,于是在已有的帮助类上做了一些改进,并将log4j的.NET版--log4net嵌入其中记录sql的执行环境和状态. 用起 ...
-
Java知多少(107)几个重要的java数据库访问类和接口
编写访问数据库的Java程序还需要几个重要的类和接口. DriverManager类 DriverManager类处理驱动程序的加载和建立新数据库连接.DriverManager是java.sql包中 ...
随机推荐
-
第一个Android应用--签证无忧 上线
用了大概1个多星期的时间,把一个简单的应用完成,[签证无忧]是基于在我所在公司办理签证的前提下,为方便客户查询进度所开发,后来我加了淘宝的购买链接,这样客人在以后需要时不需要到淘宝查找了. 签证无忧这 ...
-
Maven中多模块的编译顺序
在多模块的工程中,如果模块之间存在依赖关系,那模块的编译必须要有顺序的要求.例如:P(parent)中包含A模块和B模块,且A模块依赖于B模块,那么在P中的pom,xml中需申明为: <modu ...
-
github Mac端的使用案例
1. 本地有一个仓库,是和网页版的github连接在一起的,平时用Terminal来控制的,怎么放在github的客户端呢? 解决办法: 1.1 点击左上角的+ 号,在弹出框中选择Add,然后choo ...
-
Log4Qt 使用(一)
一.下载 http://sourceforge.net/projects/log4qt/develop 二.Log4Qt介绍 Log4Qt 是Apache Log4J 的Qt移植版,所以看Log4J的 ...
-
android百度地图打包混淆 用不了No such file or directory (2) com.baidu.mapapi.BMapManager.init(Unknown Source)
调用了百度地图地图开发包是baidumapapi_v2_1_1.jar,定位SDK版本是locSDK_3.3.jar 调试的时候能运行!可是打包签名后就运行不了! baidu google 了好久! ...
-
VS20XX-Add-In插件开发
参考文章: 1:http://www.cnblogs.com/hecool/archive/2013/04/06/3002822.html 2: http://www.cnblogs.com/ande ...
-
Java课程设计+购物车WEB页面
1. 团队名称(keke) 徐婉萍:网络1511 201521123006 2. 项目git地址 3. 项目git提交记录截图 4. 项目功能架构图与主要功能流程图 项目功能架构图 项目主要功能流程图 ...
-
使用yield和send实现简单的协程函数
使用yield和send实现协程 协程的本质是在一个线程里实现多个任务之间的来回切换,我们使用yield和send可以实现简单的协程 def pro(): print(1) n = yield &qu ...
-
NodeJS的url验证库模块url-valid
这是我10月份做的项目其中的一个部件,主要用于url检验的. 我们知道Javascript做url检验,通常是使用正则表达式来判定,其格式是否正确,例如: /^https?:\/\//.test(ur ...
-
C的内存泄漏检测
一,Windows平台下的内存泄漏检测 检测是否存在内存泄漏问题 Windows平台下面Visual Studio 调试器和 C 运行时 (CRT) 库为我们提供了检测和识别内存泄漏的有效方法,原理大 ...