我也来写:数据库访问类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,很多朋友可能会想到通过设计模式来处理,例如定义一个父类(或接口),然后各种类型的数据库继承它,然后再通过一个工厂,来创建所需要的对象;以后要增加哪种类型的数据库就再增加一个对应的类就可以了。大概是像下面这样:
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-finaly;uisng写起来比较优雅,而且在有异常的时候会自动调用对象的Disponse方法,避免有些人忘记调用。所以嵌套的 using,编译后就是嵌套的try-finaly,但其实只要我们注意在抛异常的时候释放资源,一个try-finaly即可。
3.4 DbDataReader 问题
实际项目中,我们更多的是使用DbDataReader而非DataSet/DataTable,而 DbDataReader需要自己逐行读取,这在每个调用的地方都这样写是很麻烦的,怎么解决?委托,又是它!
说到委托还有一个小小的建议,有些人喜欢自己去定义委托,但其实.net已经内置了3种委托:Func、Action、Predicate,并且提供了多个重载版本,应该优先考虑使用这些委托,在不满足的情况下,再去自定义。
3.5 在分层架构里的角色
为 DAL 层提供数据访问服务,由 DAL 直接调用;不涉及sql语句拼接、日志记录等。
四、例子
假设要调用一个 P_GetFriends存储过程,接收一个id参数,返回一个好友列表。如下:
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;
}
}
附源代码
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();
return cmd.ExecuteNonQuery();
}
finally
{
cmd.Dispose();
con.Dispose();
}
} /// <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();
return cmd.ExecuteScalar();
}
finally
{
cmd.Dispose();
con.Dispose();
}
} /// <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;
}
finally
{
adapter.Dispose();
cmd.Dispose();
con.Dispose();
}
} /// <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);
DbDataReader reader = null;
List<T> result = new List<T>();
try
{
con.Open();
reader = cmd.ExecuteReader();
while (reader.Read())
{
var item = action(reader);
result.Add(item);
}
return result;
}
finally
{
if (reader != null)
{
reader.Dispose();
}
cmd.Dispose();
con.Dispose();
}
} /// <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);
DbTransaction trans = null;
try
{
con.Open();
trans = con.BeginTransaction();
cmd.Transaction = trans;
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
{
if (trans != null)
{
trans.Rollback();
}
throw;
}
finally
{
if (trans != null)
{
trans.Dispose();
}
cmd.Dispose();
con.Dispose();
}
} #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包中 ... 
随机推荐
- js 强转规范解读
			js的强转是我们很容易遇到坑的一个地方 比如 == 会产生很有意思的事情(使用===还是最佳实践的) 或者+new Date()一个当前的数字时间戳 这里面都涉及到强转 下面分享下学习强转的过程 ... 
- AlloyRenderingEngine入门
			写在前面 AlloyRenderingEngine是一款非常快速的渲染引擎,目前该项目已经合并至 https://github.com/AlloyTeam/AlloyGameEngine/ , 属于A ... 
- 心无旁骛,向死而生:WGDC2016给创企上的一堂课
			"这是最好的时代,也是最坏的时代:这是希望的春天,也是失望的冬天." ------狄更斯 WGDC2016落幕已经一月有余,我仍然记得会议结束后,穿过高大宽敞的国家会议中心大厅,走 ... 
- 谷歌电子市场1--BaseFragment
			1.BaseFragment 共性 加载中加载失败数据为空加载成功 2.loadData调用 3.网络封装 请求网络获取数据缓存机制(写缓存和读缓存)解析数据请求网络前,先判断是否有缓存, 有的话就加 ... 
- 邓白氏码的申请-iOS公司开发者账号准备
			相比较个人开发者账号的申请,公司(or企业)的账号申请要稍显复杂,很关键的一点就是邓白氏码的申请,而在网上找了好久也没有找到相对系统的方法流程.因此, 用本文记录我在公司申请邓白氏码过程,以备之后不时 ... 
- Atitit.木马病毒websql的原理跟个设计
			Atitit.木马病毒websql的原理跟个设计 1. Keyword Wsql { var sql="select "+p.txt+" as t,"+p.v+ ... 
- 页面以base64输出图片
			<% //读取文件路径,输出base64 编码 System.IO.FileStream stream = System.IO.File.OpenRead(ViewBag.FilePath); ... 
- 一则uiautomation错误处理
			一款iphone程序,用到了多window,结果在最后一个window里设置textfield时出错 target.frontMostApp().mainWindow().textFields()[0 ... 
- linux shell for循环使用命令中读取到的值实例
			#!/bin/bash file="states" for state in `cat $file` do echo "Visit beautiful $state&qu ... 
- [Java入门笔记] 面向对象编程基础(三):成员变量和局部变量
			在类中,变量根据定义的位置不同,可以分为成员变量和局部变量. 
