我也来写:数据库访问类DBHelper(转)
一、前言
相信许多人都百度过:“.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 } |