天天看點

利用ADO.NET的體系架構打造通用的資料庫通路通用類

說明

在之前周公曾寫過針對不同資料庫的資料庫通路通用類,如針對SQLite的、針對Access的、針對Oracle的、針對SQL Server的。總結了這些通用類的通用方法,其實無非就是針對不同類型的資料庫建立Connection、Command、DataAdapter及DataReader,然後對外提供範圍ExecuteTable(),ExecuteDataReader、ExecuteScalar()及ExecuteNonQuery()方法,有了這四個方法我們就可以完成針對資料庫的所有操作了。在之前周公就曾經想過将這些資料庫通路通用類提煉出來,寫成一個針對各種資料庫通用的資料庫通用類,按照一般的方法寫的話就需要收集常用的資料庫通路類庫才能編譯,如果用反射的辦法雖然也可以解決問題,但是周公又不願意代碼裡到處都是反射的代碼,在針對.NET Framework進行分析的基礎上,寫成了今天的資料庫通路通用類。

分析

請先看下圖:

<a href="http://blog.51cto.com/attachment/201107/153741953.jpg" target="_blank"></a>

在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中的&lt;configuration&gt;&lt;system.data&gt;&lt;DbProviderFactories&gt;下添加針對這個資料庫的相關資訊),GetFactory()的兩個重載方法都是傳回一個指定的DbProviderFactor抽象類,在DbProviderFactory抽象類中又定義了建立DbConnection、DbCommand、DbDataAdapter及DbDataReader的方法。而不同的資料庫通路類程式集中又都提供了對DbProviderFactory這個抽象類的實作(包括所有由資料庫廠商提供的ADO.NET類)。是以我們要解決的問題是如何建立針對不同資料庫的DbProviderFactory這個抽象類的實作。

解決

我們知道machine.config是所有config檔案的鼻祖,包括web.config和app.config,程式在擷取配置資訊時會首先從距離自己層次關系最近的config檔案查詢起,一直到machine.config檔案為止。那麼我們就首先從自己的config檔案做章。

下面的一段代碼是從machine.config檔案中摘取出來的:

&lt;system.data&gt; 

    &lt;DbProviderFactories&gt; 

        &lt;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" /&gt; 

        &lt;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" /&gt; 

        &lt;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" /&gt; 

        &lt;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" /&gt; 

        &lt;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" /&gt; 

    &lt;/DbProviderFactories&gt; 

&lt;/system.data&gt; 

我們可以看到每個節點的組成很固定,都有name、invariant、description、type四個屬性,它們的作用分别如下:

name:資料提供程式的可識别名稱。

invariant:可以以程式設計方式用于引用資料提供程式的名稱。

description:資料提供程式的可識别描述。

type:工廠類的完全限定名,它包含用于執行個體化該對象的足夠的資訊。

注意在全局範圍類不同存在相同的invariant值,比如說在machine.config中已經定義了,就不能在自己的config檔案中重複定義。此外,在type中Version、Culture及PublicKeyToken也不是必須的。

剛剛已經說了程式在擷取配置資訊時會首先從距離自己層次關系最近的config檔案查詢起,一直到machine.config檔案為止,那麼我們可以在自己的config檔案中定義非微軟提供的資料庫通路類庫資訊,在這裡周公也提供了絕大部分非微軟提供的資料庫通路類庫資訊,如下:

&lt;add name="SQLite Data Provider" invariant="System.Data.SQLite" description=".Net Framework Data Provider for SQLite" type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" /&gt; 

&lt;add name="Informix Data Provider" invariant="IBM.Data.Informix" description=".Net Framework Data Provider for Informix" type="IBM.Data.Informix.IfxFactory, IBM.Data.Informix" /&gt; 

&lt;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" /&gt; 

&lt;add name="Firebird Data Provider" invariant="FirebirdSql.Data.FirebirdClient" description="Firebird" type="FirebirdSql.Data.FirebirdClient.FirebirdClientFactory, FirebirdSql.Data.FirebirdClient"/&gt; 

&lt;add name="Oracle Data Provider" invariant="Oracle.DataAccess.Client" description=".Net Framework Data Provider for Oracle" type="Oracle.DataAccess.Client.OracleClientFactory, Oracle.DataAccess" /&gt; 

&lt;add name="PostgreSQL Data Provider Data Provider" invariant="Npgsql" description=".Net Framework Data Provider for PostgreSQL" type="Npgsql.NpgsqlFactory, System.Data" /&gt; 

當然,也并不是在每次開發的時候都需要添加這些資訊,如果本機安裝了對應的程式集,那麼就無需配置,除此之外,對于根本不會通路的資料庫類型也不必添加對應的程式集資訊。

代碼實作

using System;  

using System.Collections.Generic;  

using System.Text;  

using System.Data;  

using System.Data.Common;  

using System.Reflection;  

using System.Text.RegularExpressions;  

/// &lt;summary&gt;  

/// 通用資料庫通路類,封裝了對資料庫的常見操作  

/// 作者:周公  

/// 日期:2011-07-18  

/// 部落格位址:http://blog.csdn.net/zhoufoxcn 或http://zhoufoxcn.blog.51cto.com  

/// 說明:(1)任何人都可以免費使用,請盡量保持此段說明。  

///      (2)這個版本還不是最終版本,有任何意見或建議請到http://weibo.com/zhoufoxcn處留言。  

/// &lt;/summary&gt;  

public sealed class DbUtility  

{  

 public string ConnectionString { get; private set; }  

 private DbProviderFactory providerFactory;  

 /// &lt;summary&gt;  

 /// 構造函數  

 /// &lt;/summary&gt;  

 /// &lt;param name="connectionString"&gt;資料庫連接配接字元串&lt;/param&gt;  

 /// &lt;param name="providerType"&gt;資料庫類型枚舉,參見&lt;paramref name="providerType"/&gt;&lt;/param&gt;  

 public DbUtility(string connectionString,DbProviderType providerType)  

 {  

  ConnectionString = connectionString;  

  providerFactory = ProviderFactory.GetDbProviderFactory(providerType);  

  if (providerFactory == null)  

  {  

   throw new ArgumentException("Can't load DbProviderFactory for given value of providerType");  

  }  

 }  

 /// &lt;summary&gt;     

 /// 對資料庫執行增删改操作,傳回受影響的行數。     

 /// &lt;/summary&gt;     

 /// &lt;param name="sql"&gt;要執行的增删改的SQL語句&lt;/param&gt;     

 /// &lt;param name="parameters"&gt;執行增删改語句所需要的參數&lt;/param&gt;  

 /// &lt;returns&gt;&lt;/returns&gt;    

 public int ExecuteNonQuery(string sql,IList&lt;DbParameter&gt; parameters)  

  return ExecuteNonQuery(sql, parameters, CommandType.Text);  

 /// &lt;param name="commandType"&gt;執行的SQL語句的類型&lt;/param&gt;  

 /// &lt;returns&gt;&lt;/returns&gt;  

 public int ExecuteNonQuery(string sql,IList&lt;DbParameter&gt; parameters, CommandType commandType)  

  using (DbCommand command = CreateDbCommand(sql, parameters, commandType))  

   command.Connection.Open();  

   int affectedRows=command.ExecuteNonQuery();  

   command.Connection.Close();  

   return affectedRows;  

 /// 執行一個查詢語句,傳回一個關聯的DataReader執行個體     

 /// &lt;param name="sql"&gt;要執行的查詢語句&lt;/param&gt;     

 /// &lt;param name="parameters"&gt;執行SQL查詢語句所需要的參數&lt;/param&gt;  

 /// &lt;returns&gt;&lt;/returns&gt;   

 public DbDataReader ExecuteReader(string sql, IList&lt;DbParameter&gt; parameters)  

  return ExecuteReader(sql, parameters, CommandType.Text);  

 public DbDataReader ExecuteReader(string sql, IList&lt;DbParameter&gt; parameters, CommandType commandType)  

  DbCommand command = CreateDbCommand(sql, parameters, commandType);  

  command.Connection.Open();  

  return command.ExecuteReader(CommandBehavior.CloseConnection);  

 /// 執行一個查詢語句,傳回一個包含查詢結果的DataTable     

 public DataTable ExecuteDataTable(string sql, IList&lt;DbParameter&gt; parameters)  

  return ExecuteDataTable(sql, parameters, CommandType.Text);  

 public DataTable ExecuteDataTable(string sql, IList&lt;DbParameter&gt; parameters, CommandType commandType)  

   using (DbDataAdapter adapter = providerFactory.CreateDataAdapter())  

   {  

    adapter.SelectCommand = command;  

    DataTable data = new DataTable();  

    adapter.Fill(data);  

    return data;  

   }  

 /// 執行一個查詢語句,傳回查詢結果的第一行第一列     

 /// &lt;param name="parameters"&gt;執行SQL查詢語句所需要的參數&lt;/param&gt;     

 /// &lt;returns&gt;&lt;/returns&gt;     

 public Object ExecuteScalar(string sql, IList&lt;DbParameter&gt; parameters)  

  return ExecuteScalar(sql, parameters, CommandType.Text);  

 public Object ExecuteScalar(string sql, IList&lt;DbParameter&gt; parameters,CommandType commandType)  

   object result = command.ExecuteScalar();  

   return result;  

 /// 建立一個DbCommand對象  

 private DbCommand CreateDbCommand(string sql, IList&lt;DbParameter&gt; parameters, CommandType commandType)  

  DbConnection connection=providerFactory.CreateConnection();  

  DbCommand command = providerFactory.CreateCommand();  

  connection.ConnectionString = ConnectionString;  

  command.CommandText = sql;  

  command.CommandType = commandType;  

  command.Connection = connection;  

  if (!(parameters == null || parameters.Count == 0))  

   foreach (DbParameter parameter in parameters)  

    command.Parameters.Add(parameter);  

  return command;  

}  

/// 資料庫類型枚舉  

public enum DbProviderType:byte 

 SqlServer,  

 MySql,  

 SQLite,  

 Oracle,  

 ODBC,  

 OleDb,  

 Firebird,  

 PostgreSql,  

 DB2,  

 Informix,  

 SqlServerCe  

/// DbProviderFactory工廠類  

public class ProviderFactory  

 private static Dictionary&lt;DbProviderType, string&gt; providerInvariantNames = new Dictionary&lt;DbProviderType, string&gt;();  

 private static Dictionary&lt;DbProviderType, DbProviderFactory&gt; providerFactoies = new Dictionary&lt;DbProviderType, DbProviderFactory&gt;(20);  

 static ProviderFactory()  

  //加載已知的資料庫通路類的程式集  

  providerInvariantNames.Add(DbProviderType.SqlServer, "System.Data.SqlClient");  

  providerInvariantNames.Add(DbProviderType.OleDb, "System.Data.OleDb");  

  providerInvariantNames.Add(DbProviderType.ODBC, "System.Data.ODBC");  

  providerInvariantNames.Add(DbProviderType.Oracle, "Oracle.DataAccess.Client");  

  providerInvariantNames.Add(DbProviderType.MySql, "MySql.Data.MySqlClient");  

  providerInvariantNames.Add(DbProviderType.SQLite, "System.Data.SQLite");  

  providerInvariantNames.Add(DbProviderType.Firebird, "FirebirdSql.Data.Firebird");  

  providerInvariantNames.Add(DbProviderType.PostgreSql, "Npgsql");  

  providerInvariantNames.Add(DbProviderType.DB2, "IBM.Data.DB2.iSeries");  

  providerInvariantNames.Add(DbProviderType.Informix, "IBM.Data.Informix");  

  providerInvariantNames.Add(DbProviderType.SqlServerCe, "System.Data.SqlServerCe");  

 /// 擷取指定資料庫類型對應的程式集名稱  

 /// &lt;param name="providerType"&gt;資料庫類型枚舉&lt;/param&gt;  

 public static string GetProviderInvariantName(DbProviderType providerType)  

  return providerInvariantNames[providerType];  

 /// 擷取指定類型的資料庫對應的DbProviderFactory  

 public static DbProviderFactory GetDbProviderFactory(DbProviderType providerType)  

  //如果還沒有加載,則加載該DbProviderFactory  

  if (!providerFactoies.ContainsKey(providerType))  

   providerFactoies.Add(providerType, ImportDbProviderFactory(providerType));  

  return providerFactoies[providerType];  

 /// 加載指定資料庫類型的DbProviderFactory  

 private static DbProviderFactory ImportDbProviderFactory(DbProviderType providerType)  

  string providerName = providerInvariantNames[providerType];  

  DbProviderFactory factory = null;  

  try 

   //從全局程式集中查找  

   factory = DbProviderFactories.GetFactory(providerName);  

  catch (ArgumentException e)  

   factory = null;  

  return factory;  

用法舉例,通路SQLite資料庫:

string connectionString = @"Data Source=D:\VS2008\NetworkTime\CrawlApplication\CrawlApplication.db3";  

string sql = "SELECT * FROM Weibo_Media order by Id desc limit 0,20000";  

DbUtility db = new DbUtility(connectionString, DbProviderType.SQLite);  

DataTable data = db.ExecuteDataTable(sql, null);  

DbDataReader reader = db.ExecuteReader(sql, null);  

reader.Close(); 

用法舉例,通路MySQL:

string connectionString = @"Server=localhost;Database=crawldb;Uid=root;Pwd=root;Port=3306;";  

DbUtility db = new DbUtility(connectionString, DbProviderType.MySql);  

從上面的代碼可以看出,使用這個通用的資料庫通用類和以前針對特定的資料庫通用類沒有什麼差別,這一切都是DbProviderFactory這個類的功勞。

總結

設計模式在程式設計中對于應對變化确實非常有用,因為在ADO.NET中存在着這麼一個工廠模式,輕而易舉地就解決了針對不同資料庫通路的問題。

本文轉自周金橋51CTO部落格,原文連結:http://blog.51cto.com/zhoufoxcn/622376 ,如需轉載請自行聯系原作者