利用ADO.NET的体系架构打造通用的数据库访问通用类

本文涉及的产品
云数据库 RDS SQL Server,独享型 2核4GB
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
简介:

说明
在之前周公曾写过针对不同数据库的数据库访问通用类,如针对SQLite的、针对Access的、针对Oracle的、针对SQL Server的。总结了这些通用类的通用方法,其实无非就是针对不同类型的数据库创建Connection、Command、DataAdapter及DataReader,然后对外提供范围ExecuteTable(),ExecuteDataReader、ExecuteScalar()及ExecuteNonQuery()方法,有了这四个方法我们就可以完成针对数据库的所有操作了。在之前周公就曾经想过将这些数据库访问通用类提炼出来,写成一个针对各种数据库通用的数据库通用类,按照一般的方法写的话就需要收集常用的数据库访问类库才能编译,如果用反射的办法虽然也可以解决问题,但是周公又不愿意代码里到处都是反射的代码,在针对.NET Framework进行分析的基础上,写成了今天的数据库访问通用类。
分析
请先看下图:
 

 

在System.Data.Common命名空间下定义了针对所有数据库的Connection、Command、DataAdapter及DataReader对象的抽象类,分别是DbConnection、DbCommand、DbDataAdapter及DbDataReader,在这些抽象类中定义了针对所有数据库的通用方法和属性,不光是在.NET Framework中微软提供的针对ODBC、OleDB、Oracle、SQL Server类中如此,在微软未提供、由数据库厂商提供的ADO.NET类也是如此(假如有一天你自己也开发了一个数据库,为了提供给.NET开发人员使用,也应该遵循这个规定)。除此之外,在System.Data.Common命名空间下还提供了两个类,一个是DbProviderFactories,另一个是DbProviderFactory,DbProviderFactories类提供的方法有两个(包括一个重载形式),GetFactoryClasses()方法返回在系统中注册的DbProviderFactory类(如果在系统中注册了,就会在machine.config中的<configuration><system.data><DbProviderFactories>下添加针对这个数据库的相关信息),GetFactory()的两个重载方法都是返回一个指定的DbProviderFactor抽象类,在DbProviderFactory抽象类中又定义了创建DbConnection、DbCommand、DbDataAdapter及DbDataReader的方法。而不同的数据库访问类程序集中又都提供了对DbProviderFactory这个抽象类的实现(包括所有由数据库厂商提供的ADO.NET类)。所以我们要解决的问题是如何创建针对不同数据库的DbProviderFactory这个抽象类的实现。
解决
我们知道machine.config是所有config文件的鼻祖,包括web.config和app.config,程序在获取配置信息时会首先从距离自己层次关系最近的config文件查询起,一直到machine.config文件为止。那么我们就首先从自己的config文件做章。
下面的一段代码是从machine.config文件中摘取出来的:
 

 
  1. <system.data> 
  2.     <DbProviderFactories> 
  3.         <add name="Odbc Data Provider" invariant="System.Data.Odbc" description=".Net Framework Data Provider for Odbc" type="System.Data.Odbc.OdbcFactory, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> 
  4.         <add name="OleDb Data Provider" invariant="System.Data.OleDb" description=".Net Framework Data Provider for OleDb" type="System.Data.OleDb.OleDbFactory, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> 
  5.         <add name="OracleClient Data Provider" invariant="System.Data.OracleClient" description=".Net Framework Data Provider for Oracle" type="System.Data.OracleClient.OracleClientFactory, System.Data.OracleClient, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> 
  6.         <add name="SqlClient Data Provider" invariant="System.Data.SqlClient" description=".Net Framework Data Provider for SqlServer" type="System.Data.SqlClient.SqlClientFactory, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> 
  7.         <add name="Microsoft SQL Server Compact Data Provider" invariant="System.Data.SqlServerCe.3.5" description=".NET Framework Data Provider for Microsoft SQL Server Compact" type="System.Data.SqlServerCe.SqlCeProviderFactory, System.Data.SqlServerCe, Version=3.5.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" /> 
  8.     </DbProviderFactories> 
  9. </system.data> 


我们可以看到每个节点的组成很固定,都有name、invariant、description、type四个属性,它们的作用分别如下:
name:数据提供程序的可识别名称。
invariant:可以以编程方式用于引用数据提供程序的名称。
description:数据提供程序的可识别描述。
type:工厂类的完全限定名,它包含用于实例化该对象的足够的信息。
注意在全局范围类不同存在相同的invariant值,比如说在machine.config中已经定义了,就不能在自己的config文件中重复定义。此外,在type中Version、Culture及PublicKeyToken也不是必须的。
刚刚已经说了程序在获取配置信息时会首先从距离自己层次关系最近的config文件查询起,一直到machine.config文件为止,那么我们可以在自己的config文件中定义非微软提供的数据库访问类库信息,在这里周公也提供了绝大部分非微软提供的数据库访问类库信息,如下:
 

 
  1. <add name="SQLite Data Provider" invariant="System.Data.SQLite" description=".Net Framework Data Provider for SQLite" type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" /> 
  2. <add name="Informix Data Provider" invariant="IBM.Data.Informix" description=".Net Framework Data Provider for Informix" type="IBM.Data.Informix.IfxFactory, IBM.Data.Informix" /> 
  3. <add name="DB2 Data Provider" invariant="IBM.Data.DB2.iSeries" description=".Net Framework Data Provider for DB2 iSeries" type="IBM.Data.DB2.iSeries.DB2Factory, IBM.Data.DB2.iSeries" /> 
  4. <add name="Firebird Data Provider" invariant="FirebirdSql.Data.FirebirdClient" description="Firebird" type="FirebirdSql.Data.FirebirdClient.FirebirdClientFactory, FirebirdSql.Data.FirebirdClient"/> 
  5. <add name="Oracle Data Provider" invariant="Oracle.DataAccess.Client" description=".Net Framework Data Provider for Oracle" type="Oracle.DataAccess.Client.OracleClientFactory, Oracle.DataAccess" /> 
  6. <add name="PostgreSQL Data Provider Data Provider" invariant="Npgsql" description=".Net Framework Data Provider for PostgreSQL" type="Npgsql.NpgsqlFactory, System.Data" /> 


当然,也并不是在每次开发的时候都需要添加这些信息,如果本机安装了对应的程序集,那么就无需配置,除此之外,对于根本不会访问的数据库类型也不必添加对应的程序集信息。
代码实现
 

 
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Text;  
  4. using System.Data;  
  5. using System.Data.Common;  
  6. using System.Reflection;  
  7. using System.Text.RegularExpressions;  
  8.  
  9. /// <summary>  
  10. /// 通用数据库访问类,封装了对数据库的常见操作  
  11. /// 作者:周公  
  12. /// 日期:2011-07-18  
  13. /// 博客地址:http://blog.csdn.net/zhoufoxcn 或http://zhoufoxcn.blog.51cto.com  
  14. /// 说明:(1)任何人都可以免费使用,请尽量保持此段说明。  
  15. ///      (2)这个版本还不是最终版本,有任何意见或建议请到http://weibo.com/zhoufoxcn处留言。  
  16. /// </summary>  
  17. public sealed class DbUtility  
  18. {  
  19.  public string ConnectionString { getprivate set; }  
  20.  private DbProviderFactory providerFactory;  
  21.  /// <summary>  
  22.  /// 构造函数  
  23.  /// </summary>  
  24.  /// <param name="connectionString">数据库连接字符串</param>  
  25.  /// <param name="providerType">数据库类型枚举,参见<paramref name="providerType"/></param>  
  26.  public DbUtility(string connectionString,DbProviderType providerType)  
  27.  {  
  28.   ConnectionString = connectionString;  
  29.   providerFactory = ProviderFactory.GetDbProviderFactory(providerType);  
  30.   if (providerFactory == null)  
  31.   {  
  32.    throw new ArgumentException("Can't load DbProviderFactory for given value of providerType");  
  33.   }  
  34.  }  
  35.  /// <summary>     
  36.  /// 对数据库执行增删改操作,返回受影响的行数。     
  37.  /// </summary>     
  38.  /// <param name="sql">要执行的增删改的SQL语句</param>     
  39.  /// <param name="parameters">执行增删改语句所需要的参数</param>  
  40.  /// <returns></returns>    
  41.  public int ExecuteNonQuery(string sql,IList<DbParameter> parameters)  
  42.  {  
  43.   return ExecuteNonQuery(sql, parameters, CommandType.Text);  
  44.  }  
  45.  /// <summary>     
  46.  /// 对数据库执行增删改操作,返回受影响的行数。     
  47.  /// </summary>     
  48.  /// <param name="sql">要执行的增删改的SQL语句</param>     
  49.  /// <param name="parameters">执行增删改语句所需要的参数</param>  
  50.  /// <param name="commandType">执行的SQL语句的类型</param>  
  51.  /// <returns></returns>  
  52.  public int ExecuteNonQuery(string sql,IList<DbParameter> parameters, CommandType commandType)  
  53.  {  
  54.   using (DbCommand command = CreateDbCommand(sql, parameters, commandType))  
  55.   {  
  56.    command.Connection.Open();  
  57.    int affectedRows=command.ExecuteNonQuery();  
  58.    command.Connection.Close();  
  59.    return affectedRows;  
  60.   }  
  61.  }  
  62.  /// <summary>     
  63.  /// 执行一个查询语句,返回一个关联的DataReader实例     
  64.  /// </summary>     
  65.  /// <param name="sql">要执行的查询语句</param>     
  66.  /// <param name="parameters">执行SQL查询语句所需要的参数</param>  
  67.  /// <returns></returns>   
  68.  public DbDataReader ExecuteReader(string sql, IList<DbParameter> parameters)  
  69.  {  
  70.   return ExecuteReader(sql, parameters, CommandType.Text);  
  71.  }  
  72.  /// <summary>     
  73.  /// 执行一个查询语句,返回一个关联的DataReader实例     
  74.  /// </summary>     
  75.  /// <param name="sql">要执行的查询语句</param>     
  76.  /// <param name="parameters">执行SQL查询语句所需要的参数</param>  
  77.  /// <param name="commandType">执行的SQL语句的类型</param>  
  78.  /// <returns></returns>   
  79.  public DbDataReader ExecuteReader(string sql, IList<DbParameter> parameters, CommandType commandType)  
  80.  {  
  81.   DbCommand command = CreateDbCommand(sql, parameters, commandType);  
  82.   command.Connection.Open();  
  83.   return command.ExecuteReader(CommandBehavior.CloseConnection);  
  84.  }  
  85.  /// <summary>     
  86.  /// 执行一个查询语句,返回一个包含查询结果的DataTable     
  87.  /// </summary>     
  88.  /// <param name="sql">要执行的查询语句</param>     
  89.  /// <param name="parameters">执行SQL查询语句所需要的参数</param>  
  90.  /// <returns></returns>  
  91.  public DataTable ExecuteDataTable(string sql, IList<DbParameter> parameters)  
  92.  {  
  93.   return ExecuteDataTable(sql, parameters, CommandType.Text);  
  94.  }  
  95.  /// <summary>     
  96.  /// 执行一个查询语句,返回一个包含查询结果的DataTable     
  97.  /// </summary>     
  98.  /// <param name="sql">要执行的查询语句</param>     
  99.  /// <param name="parameters">执行SQL查询语句所需要的参数</param>  
  100.  /// <param name="commandType">执行的SQL语句的类型</param>  
  101.  /// <returns></returns>  
  102.  public DataTable ExecuteDataTable(string sql, IList<DbParameter> parameters, CommandType commandType)  
  103.  {  
  104.   using (DbCommand command = CreateDbCommand(sql, parameters, commandType))  
  105.   {  
  106.    using (DbDataAdapter adapter = providerFactory.CreateDataAdapter())  
  107.    {  
  108.     adapter.SelectCommand = command;  
  109.     DataTable data = new DataTable();  
  110.     adapter.Fill(data);  
  111.     return data;  
  112.    }  
  113.   }  
  114.  }  
  115.  /// <summary>     
  116.  /// 执行一个查询语句,返回查询结果的第一行第一列     
  117.  /// </summary>     
  118.  /// <param name="sql">要执行的查询语句</param>     
  119.  /// <param name="parameters">执行SQL查询语句所需要的参数</param>     
  120.  /// <returns></returns>     
  121.  public Object ExecuteScalar(string sql, IList<DbParameter> parameters)  
  122.  {  
  123.   return ExecuteScalar(sql, parameters, CommandType.Text);  
  124.  }  
  125.  
  126.  /// <summary>     
  127.  /// 执行一个查询语句,返回查询结果的第一行第一列     
  128.  /// </summary>     
  129.  /// <param name="sql">要执行的查询语句</param>     
  130.  /// <param name="parameters">执行SQL查询语句所需要的参数</param>     
  131.  /// <param name="commandType">执行的SQL语句的类型</param>  
  132.  /// <returns></returns>     
  133.  public Object ExecuteScalar(string sql, IList<DbParameter> parameters,CommandType commandType)  
  134.  {  
  135.   using (DbCommand command = CreateDbCommand(sql, parameters, commandType))  
  136.   {  
  137.    command.Connection.Open();  
  138.    object result = command.ExecuteScalar();  
  139.    command.Connection.Close();  
  140.    return result;  
  141.   }  
  142.  }  
  143.  /// <summary>  
  144.  /// 创建一个DbCommand对象  
  145.  /// </summary>  
  146.  /// <param name="sql">要执行的查询语句</param>     
  147.  /// <param name="parameters">执行SQL查询语句所需要的参数</param>  
  148.  /// <param name="commandType">执行的SQL语句的类型</param>  
  149.  /// <returns></returns>  
  150.  private DbCommand CreateDbCommand(string sql, IList<DbParameter> parameters, CommandType commandType)  
  151.  {  
  152.   DbConnection connection=providerFactory.CreateConnection();  
  153.   DbCommand command = providerFactory.CreateCommand();  
  154.   connection.ConnectionString = ConnectionString;  
  155.   command.CommandText = sql;  
  156.   command.CommandType = commandType;  
  157.   command.Connection = connection;  
  158.   if (!(parameters == null || parameters.Count == 0))  
  159.   {  
  160.    foreach (DbParameter parameter in parameters)  
  161.    {  
  162.     command.Parameters.Add(parameter);  
  163.    }  
  164.   }  
  165.   return command;  
  166.  }  
  167. }  
  168. /// <summary>  
  169. /// 数据库类型枚举  
  170. /// </summary>  
  171. public enum DbProviderType:byte 
  172. {  
  173.  SqlServer,  
  174.  MySql,  
  175.  SQLite,  
  176.  Oracle,  
  177.  ODBC,  
  178.  OleDb,  
  179.  Firebird,  
  180.  PostgreSql,  
  181.  DB2,  
  182.  Informix,  
  183.  SqlServerCe  
  184. }  
  185. /// <summary>  
  186. /// DbProviderFactory工厂类  
  187. /// </summary>  
  188. public class ProviderFactory  
  189. {  
  190.  private static Dictionary<DbProviderType, string> providerInvariantNames = new Dictionary<DbProviderType, string>();  
  191.  private static Dictionary<DbProviderType, DbProviderFactory> providerFactoies = new Dictionary<DbProviderType, DbProviderFactory>(20);  
  192.  static ProviderFactory()  
  193.  {  
  194.   //加载已知的数据库访问类的程序集  
  195.   providerInvariantNames.Add(DbProviderType.SqlServer, "System.Data.SqlClient");  
  196.   providerInvariantNames.Add(DbProviderType.OleDb, "System.Data.OleDb");  
  197.   providerInvariantNames.Add(DbProviderType.ODBC, "System.Data.ODBC");  
  198.   providerInvariantNames.Add(DbProviderType.Oracle, "Oracle.DataAccess.Client");  
  199.   providerInvariantNames.Add(DbProviderType.MySql, "MySql.Data.MySqlClient");  
  200.   providerInvariantNames.Add(DbProviderType.SQLite, "System.Data.SQLite");  
  201.   providerInvariantNames.Add(DbProviderType.Firebird, "FirebirdSql.Data.Firebird");  
  202.   providerInvariantNames.Add(DbProviderType.PostgreSql, "Npgsql");  
  203.   providerInvariantNames.Add(DbProviderType.DB2, "IBM.Data.DB2.iSeries");  
  204.   providerInvariantNames.Add(DbProviderType.Informix, "IBM.Data.Informix");  
  205.   providerInvariantNames.Add(DbProviderType.SqlServerCe, "System.Data.SqlServerCe");  
  206.  }  
  207.  /// <summary>  
  208.  /// 获取指定数据库类型对应的程序集名称  
  209.  /// </summary>  
  210.  /// <param name="providerType">数据库类型枚举</param>  
  211.  /// <returns></returns>  
  212.  public static string GetProviderInvariantName(DbProviderType providerType)  
  213.  {  
  214.   return providerInvariantNames[providerType];  
  215.  }  
  216.  /// <summary>  
  217.  /// 获取指定类型的数据库对应的DbProviderFactory  
  218.  /// </summary>  
  219.  /// <param name="providerType">数据库类型枚举</param>  
  220.  /// <returns></returns>  
  221.  public static DbProviderFactory GetDbProviderFactory(DbProviderType providerType)  
  222.  {  
  223.   //如果还没有加载,则加载该DbProviderFactory  
  224.   if (!providerFactoies.ContainsKey(providerType))  
  225.   {  
  226.    providerFactoies.Add(providerType, ImportDbProviderFactory(providerType));  
  227.   }  
  228.   return providerFactoies[providerType];  
  229.  }  
  230.  /// <summary>  
  231.  /// 加载指定数据库类型的DbProviderFactory  
  232.  /// </summary>  
  233.  /// <param name="providerType">数据库类型枚举</param>  
  234.  /// <returns></returns>  
  235.  private static DbProviderFactory ImportDbProviderFactory(DbProviderType providerType)  
  236.  {  
  237.   string providerName = providerInvariantNames[providerType];  
  238.   DbProviderFactory factory = null;  
  239.   try 
  240.   {  
  241.    //从全局程序集中查找  
  242.    factory = DbProviderFactories.GetFactory(providerName);  
  243.   }  
  244.   catch (ArgumentException e)  
  245.   {  
  246.    factory = null;  
  247.   }  
  248.   return factory;  
  249.  }  


用法举例,访问SQLite数据库:
 

 
  1. string connectionString = @"Data Source=D:\VS2008\NetworkTime\CrawlApplication\CrawlApplication.db3";  
  2. string sql = "SELECT * FROM Weibo_Media order by Id desc limit 0,20000";  
  3. DbUtility db = new DbUtility(connectionString, DbProviderType.SQLite);  
  4. DataTable data = db.ExecuteDataTable(sql, null);  
  5. DbDataReader reader = db.ExecuteReader(sql, null);  
  6. reader.Close(); 


用法举例,访问MySQL:
 

 
  1. string connectionString = @"Server=localhost;Database=crawldb;Uid=root;Pwd=root;Port=3306;";  
  2. string sql = "SELECT * FROM Weibo_Media order by Id desc limit 0,20000";  
  3. DbUtility db = new DbUtility(connectionString, DbProviderType.MySql);  
  4. DataTable data = db.ExecuteDataTable(sql, null);  
  5. DbDataReader reader = db.ExecuteReader(sql, null);  
  6. reader.Close(); 


从上面的代码可以看出,使用这个通用的数据库通用类和以前针对特定的数据库通用类没有什么区别,这一切都是DbProviderFactory这个类的功劳。
总结
设计模式在编程中对于应对变化确实非常有用,因为在ADO.NET中存在着这么一个工厂模式,轻而易举地就解决了针对不同数据库访问的问题。


















本文转自周金桥51CTO博客,原文链接:http://blog.51cto.com/zhoufoxcn/622376 ,如需转载请自行联系原作者





相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
1月前
|
存储 SQL 监控
Visual Basic与数据库交互:实现数据访问和管理
【4月更文挑战第27天】本文探讨了使用Visual Basic进行数据库编程的基础,包括数据库基础、连接、数据访问技术如ADO.NET,数据绑定,事务处理,存储过程与视图。还强调了性能优化、安全性、测试与调试,以及持续维护的重要性。通过掌握这些概念和技巧,开发者能构建高效、可靠的数据驱动应用。
|
1月前
|
存储 开发框架 前端开发
前端框架EXT.NET Dotnet 3.5开发的实验室信息管理系统(LIMS)成品源码 B/S架构
发展历史:实验室信息管理系统(LIMS),就是指通过计算机网络技术对实验的各种信息进行管理的计算机软、硬件系统。也就是将计算机网络技术与现代的管理思想有机结合,利用数据处理技术、海量数据存储技术、宽带传输网络技术、自动化仪器分析技术,来对实验室的信息管理和质量控制等进行全方位管理的计算机软、硬件系统,以满足实验室管理上的各种目标(计划、控制、执行)。
33 1
|
18天前
|
消息中间件 Java 数据库连接
【消息队列开发】 对核心类实现数据库管理
【消息队列开发】 对核心类实现数据库管理
|
17天前
|
存储 Go C#
【.NET Core】深入理解IO之File类
【.NET Core】深入理解IO之File类
32 6
|
17天前
|
SQL 安全 数据库连接
sql如何访问网络数据库
访问网络数据库(通常指的是不在本地计算机上而是在网络上的数据库服务器)的SQL操作,其实与访问本地数据库在SQL语句的编写上并没有太大差异。主要的区别在于连接的设置和配置,以及如何确保网络连接的安全性
|
25天前
|
SQL 关系型数据库 分布式数据库
【PolarDB开源】PolarDB Proxy配置与优化:提升数据库访问效率
【5月更文挑战第27天】PolarDB Proxy是阿里云PolarDB的高性能数据库代理,负责SQL请求转发和负载均衡。其关键配置包括:连接池管理(如最大连接数、空闲超时时间),负载均衡策略(轮询、权重轮询、一致性哈希),以及SQL过滤规则。优化方面,关注监控与调优、缓存策略、网络优化。通过这些措施,可提升数据库访问效率和系统稳定性。
126 1
|
1月前
|
SQL 存储 安全
SQL接口如何保护数据库免受未经授权的访问?
【5月更文挑战第21天】SQL接口如何保护数据库免受未经授权的访问?
33 3
|
11天前
|
SQL Java 数据库
Java一分钟之-Spring Data JPA:简化数据库访问
【6月更文挑战第10天】Spring Data JPA是Spring Data项目的一部分,简化了Java数据库访问。它基于JPA,提供Repository接口,使开发者能通过方法命名约定自动执行SQL,减少代码量。快速上手包括添加相关依赖,配置数据库连接,并定义实体与Repository接口。常见问题涉及主键生成策略、查询方法命名和事务管理。示例展示了分页查询的使用。掌握Spring Data JPA能提升开发效率和代码质量。
33 0
|
1月前
|
架构师 开发工具 C++
最新python--类与面向对象-1,一线互联网架构师360°全方面性能调优
最新python--类与面向对象-1,一线互联网架构师360°全方面性能调优
最新python--类与面向对象-1,一线互联网架构师360°全方面性能调优
|
1月前
|
SQL 数据库连接 数据库
PyQt中数据库的访问(一)
PyQt中数据库的访问(一)
23 2