我也来写:数据库访问类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包中 ...
随机推荐
- SharePoint 2007 Full Text Searching PowerShell and CS file content with SharePoint Search
1. Ensure your site or shared folder in one Content Source. 2. Add file types. 3. The second step in ...
- AFNetworking的封装
AFNetworking的封装 特点 1.将AFNetworking 3.0封装.能够很好享受苹果开发中的面向对象开发思想 . 2.其中也提供几种方法,来请求数据.包括:GET请求/POST请求/Up ...
- 【iOS】屏幕适配之NSLayoutConstraint
前言 如何实现一张图片在iPhone和iPad上显示不同的尺寸,我了解到一般有三种办法:直接手写代码动态添加约束:把NSLayoutConstraint关联到ViewController里再viewD ...
- js判断鼠标是否停止移动
本程序实现当鼠标在一个特定的div内悬停n秒时,判断出已经停止移动. 思路: 1.定义全局变量鼠标移动状态imouse,定时器timer.当鼠标在div内移动时,imouse值为1,相反静止时值为0: ...
- rails中的session
学rails toturial的时候,第八章一直觉得有点没吃透,后来看了两篇rails关于session和cookies源码分析的文章,cookie原理与实现(rails篇) 和session原理与实 ...
- Windows Universal 应用 – Tip Calculator
声明 以下内容取材于 Bob Tabor 的课程<Windows Phone 8.1 Development for Absolute Beginners>,链接地址为:http://ww ...
- mysqldump:Couldn't execute 'show create table `tablename`': Table tablename' doesn't exist (1146)
遇到了一个错误mysqldump: Couldn't execute 'show create table `CONCURRENCY_ERRORS`': Table INVOICE_OLD.CONCU ...
- SQL SERVER中关于OR会导致索引扫描或全表扫描的浅析
在SQL SERVER的查询语句中使用OR是否会导致不走索引查找(Index Seek)或索引失效(堆表走全表扫描 (Table Scan).聚集索引表走聚集索引扫描(Clustered Index ...
- T-SQL 将存储过程结果插入到表中
解决方案1: CREATE TABLE #tmpBus ( COL1 INT, COL2 INT ) INSERT INTO #tmpBus Exec SpGetRecords 'Pa ...
- Ambari server:无法显示内存,CPU等使用率
Ambari server安装完毕后,都能正确显示各种信息.运行了几天后,发现无法显示内存,CPU等信息. 查找日志发现有错误,日志路径:/var/log/ambari-server/ambari-s ...