我也来写:数据库访问类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-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参数,返回一个好友列表。如下:

        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();
                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
    }
时间: 2024-10-24 22:41:27

我也来写:数据库访问类DBHelper的相关文章

我也来写:数据库访问类DBHelper(转)

一.前言 相信许多人都百度过:“.net 数据库访问类”.然后就出来一大堆SqlHelper.我也用过这些SqlHelper,也自己写过,一堆静态方法,开始使用起来感觉很不错,它们也确实在很多时候可以很好的工作.ADO.NET已经封装很好了,我们很容易就可以实现自己的数据库访问类. 很久前,忘记在哪里看到过了,有一个朋友写了一篇[如何做一个好用的数据库访问类](有兴趣的朋友仍然可以搜索到),这篇文章确实写得很好,作者很详细的讲解了如何设计一个好的数据库访问类:所谓“好“是指:轻量.易用.通用.高

ADO数据库访问类查询、属性扩展

1 数据库访问类查询 (1)查询一条 有参数:查询这条信息的主键: 有返回值:返回值是一个实体类: dr.read();执行一遍,读取这行信息并放进users类型中. ///返回users实体类类型 public users chayi(string name) { users u = null; com.CommandText = "select *from users where [email protected]"; com.Parameters.Clear(); com.Par

学习实践:使用模式,原则实现一个C++数据库访问类

一.概述 在我参与的多个项目中,大家使用libMySQL操作MySQL数据库,而且是源码即复用,在多个项目中有多套相同或相似的源码,这样的复用方式给开发带来了不变,而且libMySQL的使用比较麻烦,要应对很多的细节,很容易出错. 我要写一个动态链接库,将对libMySQL的操作封装起来,以二进制复用代替源码级复用:要提供线程安全的接口,用户无需关系是否加锁这样细节性的问题,减少出错及死锁的机会,当然也要允许用户自己选择是否线程安全的访问数据库:要简化访问数据库的流程,接口越简单越好. 我从20

我的C++数据库访问类

一.概述 在我参与的多个项目中,大家使用libMySQL操作MySQL数据库,而且是源码即复用,在多个项目中有多套相同或相似的源码,这样的复用方式给开发带来了不变,而且libMySQL的使用比较麻烦,要应对很多的细节,很容易出错. 我要写一个动态链接库,将对libMySQL的操作封装起来,以二进制复用代替源码级复用:要提供线程安全的接口,用户无需关系是否加锁这样细节性的问题,减少出错及死锁的机会,当然也要允许用户自己选择是否线程安全的访问数据库:要简化访问数据库的流程,接口越简单越好. 我从20

抽离CodeIgniter的数据库访问类 可以独立使用

好吧,因为组织需要,最近又开始转战php了,业务逻辑都还好说,主要是老大要求在数据访问层上加上登录态验证.其实这种要求也是合理的,互联网服务要求上层保护下层,但下层不能完全相信上层.但是问题也就来了,有如下两种方案:1.写一个mysql proxy server,用来将调用方发来的请求拼装,然后返回给调用侧.这样做的主要难度在于:a)SQL语句的拼装及序列化b)数据集序列化,虽然有不少这方面的产品,但是终究还是太过复杂,而且没有时间折腾果断放弃.2.封装一层mysql的api,调用方直接在本地调

一个C#的XML数据库访问类

原文地址:http://hankjin.blog.163.com/blog/static/33731937200942915452244/ 程序中不可避免的要用到配置文件或数据,对于数据量比较小的程序,部署数据库花费的时间就显得浪费了,因此用XML来存储不妨为一个很好的办法,而且结合C#的DataSet,我们可以很轻易的封装出一个代码简单而功能强大的数据访问类XMLConfigconfig.xml<root>  <table1>    <rowName1>hello&l

Sqlite 数据库访问类

使用Sqlite 作为软件数据库: 实现了数据库访问类 C#  使用System.Data.Sqlite 下载地址: http://system.data.sqlite.org/index.html/doc/trunk/www/downloads.wiki网站中下载Precompiled Statically-Linked Binaries for 32-bit Windows (.NET Framework 4.5)或者Precompiled Statically-Linked Binarie

Java知多少(111)几个重要的java数据库访问类和接口

修改数据表记录也有3种方案. 一.使用Statement对象 实现修改数据表记录的SQL语句的语法是:    update表名 set 字段名1 = 字段值1,字段名2 = 字段值2,……where特定条件例如: update ksInfo set 姓名 = ‘张小卫’where 姓名 = ‘张大卫’ 先创建一个SQL语句,然砶调用Statement对象的executeUpdate()方法.例如, 1 sql = “update ksInfo set 姓名 = ‘”+txtName.getTex

DataAccess通用数据库访问类,简单易用,功能强悍

以下是我编写的DataAccess通用数据库访问类,简单易用,支持:内联式创建多个参数.支持多事务提交.支持参数复用.支持更换数据库类型,希望能帮到大家,若需支持查出来后转换成实体,可以自行扩展datarow转实体类,也可以搭配dapper.net实现更强大的功能. /// <summary> /// 通用数据库访问类,支持多种数据库,无直接依赖某个数据库组件 /// 作者:左文俊 /// 日期:2016-6-3 /// </summary> public class DataAc