六、数据库适配

1.概述

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

2.设计思路

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

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

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

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}\bin\{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的格式。如果有不能解压或其它问题的请留言。

案例源码

时间: 2024-10-08 10:34:29

六、数据库适配的相关文章

国产数据库适配publiccms开源项目

金仓数据库适配 操作说明: 一.在程序的所有实体层添加schema="public"(这里的public是根据数据库定义的模式) 二.切换数据库,修改配置文件cms.properties里面的cms.dbType=kingbase(填需要更改的数据库) 如图所示: 注意:填写各数据库对应的类型:南大通用数据库 > gbasedbt   金仓数据库  >kingbase 达梦数据库 > db 三.金仓数据库工具使用 通过金仓数据库的迁移工具将其它数据库迁移的数据以及表结

MySql学习(六) —— 数据库优化理论(二) —— 查询优化技术

逻辑查询优化包括的技术 1)子查询优化  2)视图重写  3)等价谓词重写  4)条件简化  5)外连接消除  6)嵌套连接消除  7)连接消除  8)语义优化 9)非SPJ优化 一.子查询优化 1. 什么是子查询:当一个查询是另一个查询的子部分时,称之为子查询. 2. 查询的子部分,包含的情况: a) 目标列位置:子查询如果位于目标列,则只能是标量子查询,否则数据库可能返回类似“错误:子查询只能返回一个字段 ( [Err] 1242 - Subquery returns more than 1

qt 学习(六) 数据库注册用户

做什么: 1 登陆按钮按下出现注册页面, 2 输入账号  判断是否可用   查询数据库,用户名是否已经注册 3 输入密码  判断密码格式 4 输入邮箱  判断邮箱格式   查询数据库,邮箱是否已经注册 做成什么样: 怎么做: 大体是这样的: 1画ui 2 lineedit 那一栏选择信号槽,发texted信号 3 创建数据库 4 编辑槽里的判断函数 具体是这样: 1 ui设计 2 数据库放在widget.h的头文件中, 方便系统各个模块调用数据内容. 下面创建数据库 调用数据库需要的头文件 #i

step by step 之餐饮管理系统六(数据库访问模块)

距上次写的博客已经好几个月,一方面公司里面有很多的东西要学,平时的时候又要写代码,所以没有及时更新,不过现在还好,已经成型了,现在把之前的东西贴出来,先看一下现在做的几个界面吧.第一个界面是用颜色用区分台状态的,后来感觉这样没有图片好,于是用第二个界面 改进后的界面 系统登录界面. 上面的截图主要是前一段时间写的台桌界面,回到数据库访问模块吧. 数据库访问模块在网上的资源也非常的多,所以我也不想讲太多. 首先定义DataAccess层的结构,用不同的文件夹区分它们,核心的类放在DBCore文件夹

复习六——数据库完整性

数据库完整性概念 数据库完整性是指保护数据库中数据的 正确性:数据的合法性 有效性:数据是否在有效范围内 相容性:指表示同一个事实的两个数据应该一致 完整性规则定义 D(Data):约束作用的数据对象 O(Operation):触发完整性检查的数据库操作,立即检查还是延迟检查. A(Assertion):数据对象要满足的断言或语义规则 C(Condition):受A作用的数据对象值的谓词 P(Procedure):违反完整性规则时触发的过程 完整性约束按约束作用类型分类 域完整性 域是一组具有相

腾讯优测优分享干货精选| Android双卡双待适配——隐藏在数据库中的那些秘密

腾讯优测是专业的app自动化测试平台,除了提供兼容性测试,远程真机租用等多维度的测试服务,还有优分享-腾讯内部的移动研发测试干货精选~ 许多APP都希望获取用户通讯录联系人,利用通讯录关系链信息来丰富产品功能.在读取系统联系人数据库的ContentProvider时,对于双卡双待手机,电话和短信数据都需要标识来自哪张卡. Android 5.0开始加入Dual Sim支持,Android 官方方案和mtk的方案十分类似,感兴趣的小伙伴可以直接移步mtk方案实现方式. 双卡数据库适配流程 根据系统

Oracle数据库之创建和删除数据库

创建数据库 1 使用Database Configuration  Assistant工具创建Oracle数据库 步骤一 操作窗口 有4种选择  A 创建数据库 B 配置数据库选件 C 删除数据库 D 管理模板 步骤二 数据库模板 窗口 有3种选择  A 一般用途或事务处理 B 定制数据库 C 数据仓库 步骤三 数据库标识 在这一步中,需要输入 全局数据库名 和 Oracle 系统标识符(SID) 全局数据名是Oracle 数据库的唯一标识,所以不能与已有的数据库重名 打开oracle数据库时,

Oracle数据库中几种常见的SCN

控制文件中的SCN 数据文件头的SCN 数据块中的SCN 日志文件头中的SCN 事务SCN 内存中的SCN 一 控制文件中的SCN 1.1 数据库SCN 数据库SCN表示最近一次全量checkpoint操作时的SCN SQL> select checkpoint_change# from v$database; CHECKPOINT_CHANGE# ------------------        1744125 dump控制文件语法 alter session set events 'imm

11. 查询数据库各种历史记录

在SQL Server数据库中,从登陆开始,然后做了什么操作,以及数据库里发生了什么,大多都是有记录可循的,但是也有一些确实无从查起. 一. 数据库启动记录 1. 最近一次启动SQL Server的时间 select sqlserver_start_time from sys.dm_os_sys_info; --也可参考系统进程创建的时间,比服务启动时间略晚(秒级) select login_time from sysprocesses where spid = 1 select login_t