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

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

说明
在之前周公曾写过针对不同数据库的数据库访问通用类,如针对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 数据库连接 数据库
你不知道ADo.Net中操作数据库的步骤【超详细整理】
你不知道ADo.Net中操作数据库的步骤【超详细整理】
16 0
|
1月前
|
SQL 数据库 C#
C# .NET面试系列十一:数据库SQL查询(附建表语句)
#### 第1题 用一条 SQL 语句 查询出每门课都大于80 分的学生姓名 建表语句: ```sql create table tableA ( name varchar(10), kecheng varchar(10), fenshu int(11) ) DEFAULT CHARSET = 'utf8'; ``` 插入数据 ```sql insert into tableA values ('张三', '语文', 81); insert into tableA values ('张三', '数学', 75); insert into tableA values ('李四',
65 2
C# .NET面试系列十一:数据库SQL查询(附建表语句)
|
1月前
|
SQL NoSQL 前端开发
基于BS架构的饰品购物平台设计与实现(程序+文档+数据库)
基于BS架构的饰品购物平台设计与实现(程序+文档+数据库)
|
2月前
|
开发框架 Oracle 关系型数据库
ASP.NET实验室LIS系统源码 Oracle数据库
LIS是HIS的一个组成部分,通过与HIS的无缝连接可以共享HIS中的信息资源,使检验科能与门诊部、住院部、财务科和临床科室等全院各部门之间协同工作。 
37 4
|
22天前
|
存储 NoSQL Java
Java数据库编程指南:实现高效数据存储与访问
【4月更文挑战第2天】Java开发者必须掌握数据库编程,尤其是JDBC,它是连接数据库的标准接口。使用Spring JDBC或JPA能简化操作。选择合适的JDBC驱动,如MySQL Connector/J,对性能至关重要。最佳实践包括事务管理、防SQL注入、优化索引和数据库设计。NoSQL数据库如MongoDB也日益重要,Java有对应的驱动支持。理解这些概念和技术是构建高效数据库应用的基础。
Java数据库编程指南:实现高效数据存储与访问
|
3月前
|
存储 缓存 关系型数据库
鱼和熊掌如何兼得?一文解析RDS数据库存储架构升级
阿里云RDS率先推出新型存储类型通用云盘,提供低延迟、低成本、高持久性的用户体验。
鱼和熊掌如何兼得?一文解析RDS数据库存储架构升级
|
8天前
|
SQL 缓存 Java
Java数据库连接池:优化数据库访问性能
【4月更文挑战第16天】本文探讨了Java数据库连接池的重要性和优势,它能减少延迟、提高效率并增强系统的可伸缩性和稳定性。通过选择如Apache DBCP、C3P0或HikariCP等连接池技术,并进行正确配置和集成,开发者可以优化数据库访问性能。此外,批处理、缓存、索引优化和SQL调整也是提升性能的有效手段。掌握数据库连接池的使用是优化Java企业级应用的关键。
|
30天前
|
程序员 Python
类的设计奥秘:从代码到架构的科普全解
类的设计奥秘:从代码到架构的科普全解
13 2
|
1月前
|
SQL 安全 数据库连接
使用ADO.NET访问数据库
使用ADO.NET访问数据库
10 0
|
1月前
|
SQL Java 关系型数据库
Java数据库访问
Java数据库访问