zoukankan      html  css  js  c++  java
  • 六、数据库适配

    1.概述

    代码生成器需要解决的一个难题就是如何适配多种数据库。上文列出了各类数据库信息的提取,那么这里就是如何来适配不同类型的数据库了。适配数据库、封装数据这其实是ORM框架需要做的事情,所以如果觉得麻烦的可以直接使用现有的ORM框架也行。本文的核心是数据库适配不是ORM,所以不会像ORM框架那样设计的很复杂,也不涉及到对象关系映射。

    2.设计思路

    在没有用过ORM框架之前相信DBHelper是使用比较多的一种工具类,本文的数据库适配是对DBHelper的一种扩展,也即根据不同的配置来调用不同的DBHelper以满足对于不同数据库的操作需求。如图所示:

    image

    类图中定义了两个接口IDataProvider和ISchemaProvider。IDataProvider用于规范各个DBHelper,ISchemaProvider用于扩展需要获取数据库信息的DBHelper。DataProviderBase是所有DBHelper的基类。每个独立DBHelper只要继承DataProviderBase即可,如果该DBHelper需要提供数据库信息,那么就需要实现ISchemaProvider接口。

    .NET已经内置支持SqlServer(.NET内置对于Oracle的支持已经声明过期了),而其它的诸如MySql,SQLite等需要加载第三方的驱动才能使用,这样会增加适配器对于第三方程序集的依赖,所以设计时需要把依赖第三方程序集的DBHelper做成类似插件的方式。这样既减少了程序集依赖又提高了适配器的可扩展性。文件结构如图:

    image

    Brilliant.Data是适配器的核心程序集,下面的3个程序集就是剥离开的DBHelper。而SqlServer是.NET内置支持的,所以直接内置在核心程序集中。DBContext类通过工厂方式根据不同配置来创建不同的DBHelper。

    3.Provider的具体实现

    基类设计的原则就是把各个DBHelper中公共的方法提取出来,把需要子类具体实现的方法抽象出来即可,但是设计的好与坏会影响后续的代码量以及可扩展性。部分设计代码如下:

    public abstract class DataProviderBase : IDataProvider
    {
        private ConnectionInfoBase _connInfo;
    
        /// <summary>
        /// 当前连接
        /// </summary>
        protected ConnectionInfoBase ConnInfo
        {
            get { return _connInfo; }
        }
    
        /// <summary>
        /// 构造器
        /// </summary>
        public DataProviderBase() { }
    
        /// <summary>
        /// 构造器
        /// </summary>
        /// <param name="connectionString">连接字符串</param>
        public DataProviderBase(ConnectionInfoBase connInfo)
        {
            this._connInfo = connInfo;
        }
    
        /// <summary>
        /// 变更连接字符串
        /// </summary>
        /// <param name="connectionString">连接字符串</param>
        public void ChangeConnectionString(string connectionString)
        {
            ConnInfo.ConnectionString = connectionString;
        }
    
        /// <summary>
        /// 检测连接是否可用
        /// </summary>
        /// <param name="connInfo">连接</param>
        /// <returns>true:可用 false不可用</returns>
        public bool CheckConnection(ConnectionInfo connInfo)
        {
            if (String.IsNullOrEmpty(connInfo.ConnectionString))
            {
                return false;
            }
            this._connInfo = connInfo;
            using (DbConnection conn = GetConnection())
            {
                try
                {
                    if (conn.State != ConnectionState.Open)
                    {
                        conn.Open();
                    }
                    return true;
                }
                catch
                {
                    this._connInfo = null;
                    return false;
                }
            }
        }
    
        /// <summary>
        /// 返回Command对象
        /// </summary>
        private DbCommand GetCommand(DbConnection conn, SQL sql)
        {
            if (conn.State != ConnectionState.Open)
            {
                conn.Open();
            }
            DbCommand cmd = GetCommand();
            cmd.Connection = conn;
            cmd.CommandText = sql.CmdText;
            cmd.CommandType = sql.CmdType;
            if (sql.Parameters != null)
            {
                cmd.Parameters.Clear();
                cmd.Parameters.AddRange(sql.Parameters);
            }
            return cmd;
        }
    
        /// <summary>
        /// 执行查询指令返回DataSet对象
        /// </summary>
        /// <param name="sql">查询指令</param>
        /// <returns>DataSet对象</returns>
        public DataSet ExecDataSet(SQL sql)
        {
            using (DbConnection conn = GetConnection())
            {
                DbCommand cmd = GetCommand(conn, sql);
                DbDataAdapter da = GetDataAdapter();
                da.SelectCommand = cmd;
                DataSet ds = new DataSet();
                da.Fill(ds);
                cmd.Parameters.Clear();
                return ds;
            }
        }
    
        /// <summary>
        /// 执行查询指令返回DataReader对象
        /// </summary>
        /// <param name="sql">查询指令</param>
        /// <returns>DataReader对象</returns>
        public IDataReader ExecDataReader(SQL sql)
        {
            DbConnection conn = GetConnection();
            DbCommand cmd = GetCommand(conn, sql);
            IDataReader dataReader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
            cmd.Parameters.Clear();
            return dataReader;
        }
    
        /// <summary>
        /// 执行查询指令返回第一行第一列的值
        /// </summary>
        /// <param name="sql">查询指令</param>
        /// <returns>第一行第一列的值</returns>
        public object ExecScalar(SQL sql)
        {
            using (DbConnection conn = GetConnection())
            {
                DbCommand cmd = GetCommand(conn, sql);
                object result = cmd.ExecuteScalar();
                cmd.Parameters.Clear();
                //新增对于DBNull的判定,将DBnull转换为null以供逻辑判断(2014-09-05)
                return result == DBNull.Value ? null : result;
            }
        }
    
        /// <summary>
        /// 执行查询指令返回受影响行数
        /// </summary>
        /// <param name="sql">查询指令</param>
        /// <returns>受影响行数</returns>
        public int ExecNonQuerry(SQL sql)
        {
            using (DbConnection conn = GetConnection())
            {
                DbCommand cmd = GetCommand(conn, sql);
                int result = cmd.ExecuteNonQuery();
                cmd.Parameters.Clear();
                return result;
            }
        }
    
        /// <summary>
        /// 返回一个新的Connection实例
        /// </summary>
        /// <returns>Connection实例</returns>
        protected abstract DbConnection GetConnection();
    
        /// <summary>
        /// 返回一个新的Command实例
        /// </summary>
        /// <returns>Command实例</returns>
        protected abstract DbCommand GetCommand();
    
        /// <summary>
        /// 返回一个新的DataAdapter实例
        /// </summary>
        /// <returns>DataAdapter实例</returns>
        protected abstract DbDataAdapter GetDataAdapter();
    }

    都是一些比较常用的方法,如获取数据集合以及执行SQL语句。当然Sql语句并没有直接使用字符串而是使用了SQL类来封装的。此外预留了3个抽象方法用于在子类中具体实现。如果子类不实现ISchemaProvider,那么子类只要重写上述3个方法即可。以SqlServer为例实现代码如下:

    public class SqlServer : DataProviderBase, ISchemaProvider
    {
        /// <summary>
        /// 构造器
        /// </summary>
        public SqlServer() { }
    
        /// <summary>
        /// 返回一个新的Connection实例
        /// </summary>
        /// <returns>Connection实例</returns>
        protected override DbConnection GetConnection()
        {
            return new SqlConnection(ConnInfo.ConnectionString);
        }
    
        /// <summary>
        /// 返回一个新的Command实例
        /// </summary>
        /// <returns>Command实例</returns>
        protected override DbCommand GetCommand()
        {
            return new SqlCommand();
        }
    
        /// <summary>
        /// 返回一个新的DataAdapter实例
        /// </summary>
        /// <returns>DataAdapter实例</returns>
        protected override DbDataAdapter GetDataAdapter()
        {
            return new SqlDataAdapter();
        }
    }

    其余的诸如Oracle,MySql,SQLite的实现方式类似,这里就不贴具体代码了,详细的实现方式请参照源码。ISchemaProvider是用于扩展Provider的功能的。该接口定义一组方法用于获取数据库信息用的。这些信息在上文中已经讲解过,也是做代码生成器必不可少的。为什么这里要单独提取一个接口用来获取数据库信息,而不集成在基类或者IDataProvider接口中呢?如类图所描述的,并不是所有的Provider都能过提供数据库信息的。所以为了考虑那些不能提供数据库信息的Provider只能单独提取出一个接口了。关于Provider实现ISchemaProvider的细节请参照源码的实现。

    4.动态创建Provider实例

    /// <summary>
    /// 创建Provider对象
    /// </summary>
    /// <param name="providerName">provider名称</param>
    /// <returns>Provider对象</returns>
    private object CreateProvider(string providerName)
    {
        Type type = null;
        if (!String.IsNullOrEmpty(providerName))
        {
            string namePrefix = "Brilliant.Data.Provider.";
            if (!providerName.Contains(namePrefix))
            {
                providerName = namePrefix + providerName;
            }
            string assemblyPath = String.Empty;
            if (providerName != typeof(SqlServer).FullName)
            {
                assemblyPath = String.Format("{0}.dll", providerName);
                if (!File.Exists(assemblyPath))
                {
                    assemblyPath = String.Format(@"{0}in{1}.dll", AppDomain.CurrentDomain.BaseDirectory, providerName);
                    if (!File.Exists(assemblyPath))
                    {
                        throw new Exception(String.Format("目标文件"{0}"不存在!", assemblyPath));
                    }
                }
                Assembly assembly = Assembly.LoadFrom(assemblyPath);
                type = assembly.GetType(providerName);
            }
            else
            {
                type = Type.GetType(providerName, true);
            }
        }
        else
        {
            type = typeof(SqlServer);
        }
        return Activator.CreateInstance(type);
    }

    在DBContext中添加上述方法,本案例是根据config配置文件来动态创建DBHelper实例的。

    <configuration>
      <connectionStrings>
        <add name="SqlServer" providerName="Brilliant.Data.Provider.SqlServer" connectionString="Data Source=192.168.1.101;Database=DB_Test;uid=sa;pwd=123"/>
      </connectionStrings>
    </configuration>

    其中providerName提供了指定Provider类的完整引用路径。上述方法就是依据该路径使用反射在DBContext初始化时动态创建Provider对象。大致原理就是这样,至于实现方式有很多种,依据不同的需求可以有不同的方式。给定的案例源码中有一个完整的实现案例可以参考。该实例中的Provider现了ISchemaProvider接口,同时加入不少后续要使用到的基础方法。稍微比文章中讲解的复杂,但是核心内容基本是一样的。该文章是针对传统DBHelper的扩展提供了一种可行性的思路。一般ORM框架除了对象关系映射之外,多数据库适配也是其必不可少的功能。所以了解多数据库适配也是后续了解ORM框架所不可或缺的。

    为了节约空间和上传方便,使用了7z的格式。如果有不能解压或其它问题的请留言。

    案例源码

  • 相关阅读:
    iOS 针对txt文档进行解码
    iOS导入其他APP下载的文件(用其他应用打开)
    地图定位
    NSURLSession
    利用box-shadow属性实现页面层叠效果
    利用vue-router和compoment重构代码--踩坑(一)
    markdown 一分钟入门
    webpack,配置,上手,例子
    在搜索框里添加放大镜的图标,且显示提示信息
    css3实现checkbox变开关按钮
  • 原文地址:https://www.cnblogs.com/UltimateAvalon/p/4658970.html
Copyright © 2011-2022 走看看